mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-08-24 21:17:31 +00:00
feat: add support for response_mode=fragment
This commit is contained in:
@@ -92,11 +92,11 @@ func ValidateCallbackURLPattern(raw string) bool {
|
||||
}
|
||||
|
||||
// ValidateResponseMode validates response_mode parameter
|
||||
// If responseMode is present, it must be "form_post" or "query"
|
||||
// If responseMode is present, it must be "form_post", "query", or "fragment"
|
||||
// Empty responseMode is allowed (field not provided, use default)
|
||||
func ValidateResponseMode(responseMode string) bool {
|
||||
switch responseMode {
|
||||
case "form_post", "query":
|
||||
case "form_post", "query", "fragment":
|
||||
return true
|
||||
case "":
|
||||
return true
|
||||
|
||||
@@ -66,8 +66,9 @@ func TestValidateResponseMode(t *testing.T) {
|
||||
}{
|
||||
{"valid form_post", "form_post", true},
|
||||
{"valid query", "query", true},
|
||||
{"valid fragment", "fragment", true},
|
||||
{"valid empty", "", true},
|
||||
{"invalid fragment", "fragment", false},
|
||||
{"invalid unknown", "unknown", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -180,11 +180,10 @@
|
||||
throw new Error('Invalid redirect URL protocol');
|
||||
}
|
||||
|
||||
redirectURL.searchParams.append('error', error);
|
||||
if (authorizeState) {
|
||||
redirectURL.searchParams.append('state', authorizeState);
|
||||
}
|
||||
window.location.href = redirectURL.toString();
|
||||
window.location.href = createRedirectURL(callbackURL, {
|
||||
error,
|
||||
state: authorizeState
|
||||
});
|
||||
}
|
||||
|
||||
function onSuccess(code: string, callbackURL: string, issuer: string) {
|
||||
@@ -193,10 +192,6 @@
|
||||
throw new Error('Invalid redirect URL protocol');
|
||||
}
|
||||
|
||||
redirectURL.searchParams.append('code', code);
|
||||
redirectURL.searchParams.append('state', authorizeState);
|
||||
redirectURL.searchParams.append('iss', issuer);
|
||||
|
||||
success = true;
|
||||
setTimeout(() => {
|
||||
if (responseMode === 'form_post') {
|
||||
@@ -231,16 +226,34 @@
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
} else {
|
||||
// Default query parameter redirect (response_mode=query or not specified)
|
||||
const redirectURL = new URL(callbackURL);
|
||||
redirectURL.searchParams.append('code', code);
|
||||
redirectURL.searchParams.append('state', authorizeState);
|
||||
redirectURL.searchParams.append('iss', issuer);
|
||||
|
||||
window.location.href = redirectURL.toString();
|
||||
window.location.href = createRedirectURL(callbackURL, {
|
||||
code,
|
||||
state: authorizeState,
|
||||
iss: issuer
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function createRedirectURL(url: string, params: Record<string, string>) {
|
||||
const redirectURL = new URL(url);
|
||||
const responseParams =
|
||||
responseMode === 'fragment'
|
||||
? new URLSearchParams(redirectURL.hash.slice(1))
|
||||
: redirectURL.searchParams;
|
||||
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value) {
|
||||
responseParams.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (responseMode === 'fragment') {
|
||||
redirectURL.hash = responseParams.toString();
|
||||
}
|
||||
|
||||
return redirectURL.toString();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
@@ -671,12 +671,45 @@ test('Authorize existing client with response_mode=form_post', async ({ page })
|
||||
await expectFormPostRequest(formPostRequestPromise);
|
||||
});
|
||||
|
||||
test('Authorize existing client with response_mode=fragment', async ({ page }) => {
|
||||
const oidcClient = oidcClients.nextcloud;
|
||||
const urlParams = createUrlParams(oidcClient);
|
||||
urlParams.set('response_mode', 'fragment');
|
||||
|
||||
await page.goto(`/authorize?${urlParams.toString()}`);
|
||||
|
||||
const redirectUrl = await waitForCallbackURL(page, oidcClient.callbackUrl);
|
||||
expect(redirectUrl.search).toBe('');
|
||||
|
||||
const fragmentParams = new URLSearchParams(redirectUrl.hash.slice(1));
|
||||
expect(fragmentParams.get('code')).toBeTruthy();
|
||||
expect(fragmentParams.get('state')).toBe('nXx-6Qr-owc1SHBa');
|
||||
expect(fragmentParams.get('iss')).toBeTruthy();
|
||||
});
|
||||
|
||||
function waitForFormPostRequest(page: Page, callbackUrl: string): Promise<Request> {
|
||||
return page.waitForRequest(
|
||||
(request) => request.method() === 'POST' && request.url() === callbackUrl
|
||||
);
|
||||
}
|
||||
|
||||
async function waitForCallbackURL(page: Page, callbackUrl: string): Promise<URL> {
|
||||
const expectedUrl = new URL(callbackUrl);
|
||||
|
||||
await page
|
||||
.waitForURL((url) => url.origin === expectedUrl.origin && url.pathname === expectedUrl.pathname)
|
||||
.catch((e) => {
|
||||
if (
|
||||
!e.message.includes('net::ERR_NAME_NOT_RESOLVED') &&
|
||||
!e.message.includes('net::ERR_CERT_AUTHORITY_INVALID')
|
||||
) {
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
return new URL(page.url());
|
||||
}
|
||||
|
||||
async function expectFormPostRequest(formPostRequestPromise: Promise<Request>) {
|
||||
const request = await formPostRequestPromise;
|
||||
const formData = new URLSearchParams(request.postData() ?? '');
|
||||
@@ -704,6 +737,23 @@ test.describe('OIDC prompt parameter', () => {
|
||||
expect(redirectUrl.searchParams.get('state')).toBe('nXx-6Qr-owc1SHBa');
|
||||
});
|
||||
|
||||
test('prompt=none redirects errors with response_mode=fragment', async ({ page }) => {
|
||||
await page.context().clearCookies();
|
||||
const oidcClient = oidcClients.nextcloud;
|
||||
const urlParams = createUrlParams(oidcClient);
|
||||
urlParams.set('prompt', 'none');
|
||||
urlParams.set('response_mode', 'fragment');
|
||||
|
||||
await page.goto(`/authorize?${urlParams.toString()}`).then(() => {});
|
||||
|
||||
const redirectUrl = await waitForCallbackURL(page, oidcClient.callbackUrl);
|
||||
expect(redirectUrl.search).toBe('');
|
||||
|
||||
const fragmentParams = new URLSearchParams(redirectUrl.hash.slice(1));
|
||||
expect(fragmentParams.get('error')).toBe('login_required');
|
||||
expect(fragmentParams.get('state')).toBe('nXx-6Qr-owc1SHBa');
|
||||
});
|
||||
|
||||
test('prompt=none redirects with consent_required when authorization needed', async ({
|
||||
page
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user