diff --git a/client/src/pages/OAuthAuthorizePage.tsx b/client/src/pages/OAuthAuthorizePage.tsx
index 6320bb84..1e4e0d4e 100644
--- a/client/src/pages/OAuthAuthorizePage.tsx
+++ b/client/src/pages/OAuthAuthorizePage.tsx
@@ -22,6 +22,7 @@ export default function OAuthAuthorizePage(): React.ReactElement {
const [validation, setValidation] = useState(null)
const [submitting, setSubmitting] = useState(false)
const [errorMsg, setErrorMsg] = useState(null)
+ const [selectedScopes, setSelectedScopes] = useState([])
const params = new URLSearchParams(window.location.search)
const clientId = params.get('client_id') || ''
@@ -68,12 +69,14 @@ export default function OAuthAuthorizePage(): React.ReactElement {
}
if (!result.consentRequired) {
- // Consent already on record — auto-approve silently
+ // Consent already on record — auto-approve silently with the full validated scope
setPageState('auto_approving')
- await submitConsent(true)
+ await submitConsent(true, result.scopes ?? [])
return
}
+ // Pre-select all scopes the client is requesting — user can deselect
+ setSelectedScopes(result.scopes ?? [])
setPageState('consent')
} catch (err: unknown) {
setPageState('error')
@@ -81,13 +84,14 @@ export default function OAuthAuthorizePage(): React.ReactElement {
}
}
- async function submitConsent(approved: boolean) {
+ async function submitConsent(approved: boolean, scopes: string[] = selectedScopes) {
setSubmitting(true)
try {
const result = await oauthApi.authorize({
client_id: clientId,
redirect_uri: redirectUri,
- scope,
+ // When approving, send only the scopes the user selected; deny uses original scope
+ scope: approved ? scopes.join(' ') : scope,
state,
code_challenge: codeChallenge,
code_challenge_method: ccMethod,
@@ -102,6 +106,20 @@ export default function OAuthAuthorizePage(): React.ReactElement {
}
}
+ function toggleScope(s: string) {
+ setSelectedScopes(prev =>
+ prev.includes(s) ? prev.filter(x => x !== s) : [...prev, s]
+ )
+ }
+
+ function toggleGroup(groupScopes: string[], allSelected: boolean) {
+ setSelectedScopes(prev =>
+ allSelected
+ ? prev.filter(s => !groupScopes.includes(s))
+ : [...new Set([...prev, ...groupScopes])]
+ )
+ }
+
function handleLoginRedirect() {
const next = '/oauth/authorize?' + params.toString()
window.location.href = '/login?redirect=' + encodeURIComponent(next)
@@ -198,10 +216,14 @@ export default function OAuthAuthorizePage(): React.ReactElement {