mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-10-26 02:29:08 +01:00
fix: callback URL validation not validated if prompt=none
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { ListRequestOptions, Paginated } from '$lib/types/list-request.type';
|
||||
import type {
|
||||
AccessibleOidcClient,
|
||||
AuthorizeCallbackResponse,
|
||||
AuthorizeResponse,
|
||||
OidcClient,
|
||||
OidcClientCreate,
|
||||
@@ -44,6 +45,20 @@ class OidcService extends APIService {
|
||||
return res.data as AuthorizeResponse;
|
||||
};
|
||||
|
||||
resolveAuthorizeCallbackURL = async (
|
||||
clientId: string,
|
||||
callbackURL: string,
|
||||
requestURI?: string
|
||||
) => {
|
||||
const res = await this.api.post('/oidc/authorize/callback-url', {
|
||||
clientId,
|
||||
callbackURL,
|
||||
requestURI
|
||||
});
|
||||
|
||||
return res.data as AuthorizeCallbackResponse;
|
||||
};
|
||||
|
||||
isAuthorizationRequired = async (clientId: string, scope: string, requestURI?: string) => {
|
||||
const res = await this.api.post('/oidc/authorization-required', {
|
||||
scope,
|
||||
|
||||
@@ -70,6 +70,10 @@ export type AuthorizeResponse = {
|
||||
requiresRedirect?: boolean;
|
||||
};
|
||||
|
||||
export type AuthorizeCallbackResponse = {
|
||||
callbackURL: string;
|
||||
};
|
||||
|
||||
export type AccessibleOidcClient = OidcClientMetaData & {
|
||||
lastUsedAt: Date | null;
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import userStore from '$lib/stores/user-store';
|
||||
import { cachedProfilePicture } from '$lib/utils/cached-image-util';
|
||||
import { getWebauthnErrorMessage } from '$lib/utils/error-util';
|
||||
import { getAxiosErrorMessage, getWebauthnErrorMessage } from '$lib/utils/error-util';
|
||||
import { startAuthentication, type AuthenticationResponseJSON } from '@simplewebauthn/browser';
|
||||
import { onMount } from 'svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
@@ -64,15 +64,19 @@
|
||||
const hasPromptSelectAccount = promptValues.includes('select_account');
|
||||
|
||||
onMount(() => {
|
||||
void handleInitialAuthorization();
|
||||
});
|
||||
|
||||
async function handleInitialAuthorization() {
|
||||
// Conflicting prompt values - none can't be combined with any interactive prompt
|
||||
if (hasPromptNone && (hasPromptConsent || hasPromptLogin || hasPromptSelectAccount)) {
|
||||
redirectWithError('interaction_required');
|
||||
await redirectWithError('interaction_required');
|
||||
return;
|
||||
}
|
||||
|
||||
// If prompt=none and user is not signed in, redirect immediately with login_required
|
||||
if (hasPromptNone && !$userStore) {
|
||||
redirectWithError('login_required');
|
||||
await redirectWithError('login_required');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -85,9 +89,9 @@
|
||||
}
|
||||
|
||||
if ($userStore) {
|
||||
authorize();
|
||||
await authorize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function useDifferentAccount() {
|
||||
try {
|
||||
@@ -130,7 +134,7 @@
|
||||
|
||||
// If prompt=none and consent required, redirect with error
|
||||
if (hasPromptNone && authorizationRequired) {
|
||||
redirectWithError('consent_required');
|
||||
await redirectWithError('consent_required');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,7 +173,7 @@
|
||||
// Check if backend returned a redirect error
|
||||
if (result.requiresRedirect && result.error) {
|
||||
if (hasPromptNone) {
|
||||
redirectWithError(result.error);
|
||||
await redirectWithError(result.error, result.callbackURL);
|
||||
} else {
|
||||
errorMessage = result.error;
|
||||
isLoading = false;
|
||||
@@ -184,16 +188,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
function redirectWithError(error: string) {
|
||||
const redirectURL = new URL(callbackURL);
|
||||
if (redirectURL.protocol == 'javascript:' || redirectURL.protocol == 'data:') {
|
||||
throw new Error('Invalid redirect URL protocol');
|
||||
}
|
||||
async function redirectWithError(error: string, validatedCallbackURL?: string) {
|
||||
isLoading = true;
|
||||
|
||||
window.location.href = createRedirectURL(callbackURL, {
|
||||
error,
|
||||
state: authorizeState
|
||||
});
|
||||
try {
|
||||
const safeCallbackURL =
|
||||
validatedCallbackURL ||
|
||||
(await oidService.resolveAuthorizeCallbackURL(client!.id, callbackURL, requestURI))
|
||||
.callbackURL;
|
||||
const redirectURL = new URL(safeCallbackURL);
|
||||
if (redirectURL.protocol == 'javascript:' || redirectURL.protocol == 'data:') {
|
||||
throw new Error('Invalid redirect URL protocol');
|
||||
}
|
||||
|
||||
window.location.href = createRedirectURL(safeCallbackURL, {
|
||||
error,
|
||||
state: authorizeState
|
||||
});
|
||||
} catch (e) {
|
||||
errorMessage = getAxiosErrorMessage(e);
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onSuccess(code: string, callbackURL: string, issuer: string) {
|
||||
|
||||
Reference in New Issue
Block a user