-
Notifications
You must be signed in to change notification settings - Fork 374
Improve key missing error handling with custom hooks #6521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,7 +52,7 @@ import type { AuthProtect } from './protect'; | |
import { createProtect } from './protect'; | ||
import type { NextMiddlewareEvtParam, NextMiddlewareRequestParam, NextMiddlewareReturn } from './types'; | ||
import { | ||
assertKey, | ||
// assertKey, // removed direct usage for keys to allow non-throwing behavior | ||
decorateRequest, | ||
handleMultiDomainAndProxy, | ||
redirectAdapter, | ||
|
@@ -93,6 +93,22 @@ export interface ClerkMiddlewareOptions extends AuthenticateAnyRequestOptions { | |
* When set, automatically injects a Content-Security-Policy header(s) compatible with Clerk. | ||
*/ | ||
contentSecurityPolicy?: ContentSecurityPolicyOptions; | ||
|
||
/** | ||
* Optional hook invoked when the publishable key is missing. If it returns a response, it will be used as the middleware result. | ||
* If it returns void, the request will continue unauthenticated. | ||
*/ | ||
onMissingPublishableKey?: ( | ||
request: NextRequest, | ||
) => NextMiddlewareReturn | Promise<NextMiddlewareReturn> | void | Promise<void>; | ||
|
||
/** | ||
* Optional hook invoked when the secret key is missing. If it returns a response, it will be used as the middleware result. | ||
* If it returns void, the request will continue unauthenticated. | ||
*/ | ||
onMissingSecretKey?: ( | ||
request: NextRequest, | ||
) => NextMiddlewareReturn | Promise<NextMiddlewareReturn> | void | Promise<void>; | ||
Comment on lines
+96
to
+111
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand the usage correctly those should only be executed in DEV. Also if we are thinking these hooks as something that will enable extra Clerk functionality we should prefix them with |
||
} | ||
|
||
type ClerkMiddlewareOptionsCallback = (req: NextRequest) => ClerkMiddlewareOptions | Promise<ClerkMiddlewareOptions>; | ||
|
@@ -141,14 +157,48 @@ export const clerkMiddleware = ((...args: unknown[]): NextMiddleware | NextMiddl | |
|
||
const keyless = await getKeylessCookieValue(name => request.cookies.get(name)?.value); | ||
|
||
const publishableKey = assertKey( | ||
resolvedParams.publishableKey || PUBLISHABLE_KEY || keyless?.publishableKey, | ||
() => errorThrower.throwMissingPublishableKeyError(), | ||
); | ||
// Resolve keys without throwing | ||
const resolvedPublishableKey = resolvedParams.publishableKey || PUBLISHABLE_KEY || keyless?.publishableKey; | ||
const resolvedSecretKey = resolvedParams.secretKey || SECRET_KEY || keyless?.secretKey; | ||
|
||
const secretKey = assertKey(resolvedParams.secretKey || SECRET_KEY || keyless?.secretKey, () => | ||
errorThrower.throwMissingSecretKeyError(), | ||
); | ||
// Handle missing publishable key | ||
if (!resolvedPublishableKey) { | ||
if (resolvedParams.onMissingPublishableKey) { | ||
const maybeResponse = await resolvedParams.onMissingPublishableKey(request); | ||
if (maybeResponse) { | ||
return maybeResponse; | ||
} | ||
// Fall through to allow request to continue unauthenticated | ||
const res = NextResponse.next(); | ||
setRequestHeadersOnNextResponse(res, request, { | ||
[constants.Headers.AuthStatus]: 'signed-out', | ||
}); | ||
return res; | ||
} | ||
Comment on lines
+171
to
+177
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you clarify how this operates on the client ?
|
||
// Default behavior remains throwing if no hook is provided | ||
return errorThrower.throwMissingPublishableKeyError(); | ||
} | ||
|
||
// Handle missing secret key | ||
if (!resolvedSecretKey) { | ||
if (resolvedParams.onMissingSecretKey) { | ||
const maybeResponse = await resolvedParams.onMissingSecretKey(request); | ||
if (maybeResponse) { | ||
return maybeResponse; | ||
} | ||
// Fall through to allow request to continue unauthenticated | ||
const res = NextResponse.next(); | ||
setRequestHeadersOnNextResponse(res, request, { | ||
[constants.Headers.AuthStatus]: 'signed-out', | ||
}); | ||
return res; | ||
} | ||
// Default behavior remains throwing if no hook is provided | ||
return errorThrower.throwMissingSecretKeyError(); | ||
} | ||
|
||
const publishableKey = resolvedPublishableKey; | ||
const secretKey = resolvedSecretKey; | ||
const signInUrl = resolvedParams.signInUrl || SIGN_IN_URL; | ||
const signUpUrl = resolvedParams.signUpUrl || SIGN_UP_URL; | ||
|
||
|
@@ -158,7 +208,7 @@ export const clerkMiddleware = ((...args: unknown[]): NextMiddleware | NextMiddl | |
signInUrl, | ||
signUpUrl, | ||
...resolvedParams, | ||
}; | ||
} as ClerkMiddlewareOptions; | ||
|
||
// Propagates the request data to be accessed on the server application runtime from helpers such as `clerkClient` | ||
clerkMiddlewareRequestDataStore.set('requestData', options); | ||
|
@@ -198,7 +248,7 @@ export const clerkMiddleware = ((...args: unknown[]): NextMiddleware | NextMiddl | |
const locationHeader = requestState.headers.get(constants.Headers.Location); | ||
if (locationHeader) { | ||
const res = NextResponse.redirect(locationHeader); | ||
requestState.headers.forEach((value, key) => { | ||
requestState.headers.forEach((value: string, key: string) => { | ||
if (key === constants.Headers.Location) { | ||
return; | ||
} | ||
|
@@ -247,7 +297,7 @@ export const clerkMiddleware = ((...args: unknown[]): NextMiddleware | NextMiddl | |
// TODO @nikos: we need to make this more generic | ||
// and move the logic in clerk/backend | ||
if (requestState.headers) { | ||
requestState.headers.forEach((value, key) => { | ||
requestState.headers.forEach((value: string, key: string) => { | ||
if (key === constants.Headers.ContentSecurityPolicy) { | ||
logger.debug('Content-Security-Policy detected', () => ({ | ||
value, | ||
|
@@ -259,7 +309,7 @@ export const clerkMiddleware = ((...args: unknown[]): NextMiddleware | NextMiddl | |
|
||
if (isRedirect(handlerResult)) { | ||
logger.debug('handlerResult is redirect'); | ||
return serverRedirectWithAuth(clerkRequest, handlerResult, options); | ||
return serverRedirectWithAuth(clerkRequest, handlerResult, { secretKey }); | ||
} | ||
|
||
if (options.debug) { | ||
|
@@ -270,10 +320,10 @@ export const clerkMiddleware = ((...args: unknown[]): NextMiddleware | NextMiddl | |
// Only pass keyless credentials when there are no explicit keys | ||
secretKey === keyless?.secretKey | ||
? { | ||
publishableKey: keyless?.publishableKey, | ||
secretKey: keyless?.secretKey, | ||
publishableKey: keyless?.publishableKey || '', | ||
secretKey: keyless?.secretKey || '', | ||
} | ||
: {}; | ||
: { publishableKey: '', secretKey: '' }; | ||
|
||
decorateRequest( | ||
clerkRequest, | ||
|
@@ -514,7 +564,7 @@ const handleControlFlowErrors = ( | |
sessionStatus: requestState.toAuth()?.sessionStatus, | ||
}); | ||
|
||
const { returnBackUrl } = e; | ||
const { returnBackUrl } = e as any; | ||
return redirect[isRedirectToSignIn ? 'redirectToSignIn' : 'redirectToSignUp']({ returnBackUrl }); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// tsup.config.ts | ||
import { defineConfig } from 'tsup'; | ||
var tsup_config_default = defineConfig(() => { | ||
return { | ||
entry: { | ||
index: 'src/index.ts', | ||
}, | ||
minify: false, | ||
clean: true, | ||
sourcemap: true, | ||
format: ['cjs', 'esm'], | ||
legacyOutput: true, | ||
dts: true, | ||
}; | ||
}); | ||
export { tsup_config_default as default }; | ||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidHN1cC5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL3dvcmtzcGFjZS9wYWNrYWdlcy90eXBlcy90c3VwLmNvbmZpZy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvd29ya3NwYWNlL3BhY2thZ2VzL3R5cGVzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy93b3Jrc3BhY2UvcGFja2FnZXMvdHlwZXMvdHN1cC5jb25maWcudHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd0c3VwJztcblxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKCgpID0+IHtcbiAgcmV0dXJuIHtcbiAgICBlbnRyeToge1xuICAgICAgaW5kZXg6ICdzcmMvaW5kZXgudHMnLFxuICAgIH0sXG4gICAgbWluaWZ5OiBmYWxzZSxcbiAgICBjbGVhbjogdHJ1ZSxcbiAgICBzb3VyY2VtYXA6IHRydWUsXG4gICAgZm9ybWF0OiBbJ2NqcycsICdlc20nXSxcbiAgICBsZWdhY3lPdXRwdXQ6IHRydWUsXG4gICAgZHRzOiB0cnVlLFxuICB9O1xufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQXlOLFNBQVMsb0JBQW9CO0FBRXRQLElBQU8sc0JBQVEsYUFBYSxNQUFNO0FBQ2hDLFNBQU87QUFBQSxJQUNMLE9BQU87QUFBQSxNQUNMLE9BQU87QUFBQSxJQUNUO0FBQUEsSUFDQSxRQUFRO0FBQUEsSUFDUixPQUFPO0FBQUEsSUFDUCxXQUFXO0FBQUEsSUFDWCxRQUFRLENBQUMsT0FBTyxLQUFLO0FBQUEsSUFDckIsY0FBYztBQUFBLElBQ2QsS0FBSztBQUFBLEVBQ1A7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo= | ||
Comment on lines
+1
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. was this autogenerated ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Why is Keyless being affected by these changes ?