diff --git a/id.saladeaula.digital/app/routes/authorize.py b/id.saladeaula.digital/app/routes/authorize.py index f55757f..3abca6e 100644 --- a/id.saladeaula.digital/app/routes/authorize.py +++ b/id.saladeaula.digital/app/routes/authorize.py @@ -1,4 +1,3 @@ -from http import HTTPStatus from http.cookies import SimpleCookie import jwt @@ -41,10 +40,9 @@ def authorize(): client_scopes = set(scope_to_list(grant.client.scope)) user_scopes = set(scope_to_list(session_scope)) if session_scope else set() - # Deny authorization if user has no scopes matching the client request - if not user_scopes & client_scopes: - raise ForbiddenError() - # raise errors.InvalidScopeError(status_code=HTTPStatus.UNAUTHORIZED) + # Deny authorization if user lacks scopes requested by client + if not client_scopes.issubset(user_scopes): + raise ForbiddenError('Access denied') return server.create_authorization_response( request=router.current_event, diff --git a/id.saladeaula.digital/client/app/lib/http-status.ts b/id.saladeaula.digital/client/app/lib/http-status.ts index 1b52be5..b4c6042 100644 --- a/id.saladeaula.digital/client/app/lib/http-status.ts +++ b/id.saladeaula.digital/client/app/lib/http-status.ts @@ -2,4 +2,5 @@ export const OK = 200 export const FOUND = 302 export const BAD_REQUEST = 400 export const UNAUTHORIZED = 401 +export const FORBIDDEN = 403 export const INTERNAL_SERVER = 500 diff --git a/id.saladeaula.digital/client/app/routes.ts b/id.saladeaula.digital/client/app/routes.ts index f03ed00..af69e34 100644 --- a/id.saladeaula.digital/client/app/routes.ts +++ b/id.saladeaula.digital/client/app/routes.ts @@ -6,7 +6,10 @@ import { } from '@react-router/dev/routes' export default [ - layout('routes/layout.tsx', [index('routes/index.tsx')]), + layout('routes/layout.tsx', [ + index('routes/index.tsx'), + route('/deny', 'routes/deny.tsx') + ]), route('/authorize', 'routes/authorize.ts'), route('/token', 'routes/token.ts'), route('/revoke', 'routes/revoke.ts') diff --git a/id.saladeaula.digital/client/app/routes/authorize.ts b/id.saladeaula.digital/client/app/routes/authorize.ts index d0e409f..9942b14 100644 --- a/id.saladeaula.digital/client/app/routes/authorize.ts +++ b/id.saladeaula.digital/client/app/routes/authorize.ts @@ -30,6 +30,12 @@ export async function loader({ request, context }: Route.LoaderArgs) { redirect: 'manual' }) + console.log('Issuer response', { + json: await r.json(), + headers: r.headers, + status: r.status + }) + if (r.status === httpStatus.FOUND) { return new Response(await r.text(), { status: r.status, @@ -37,11 +43,15 @@ export async function loader({ request, context }: Route.LoaderArgs) { }) } - console.log('Issuer response', { - json: await r.json(), - headers: r.headers, - status: r.status - }) + // Deny authorization if user lacks scopes requested by client + if (r.status === httpStatus.FORBIDDEN) { + return new Response(null, { + status: httpStatus.FOUND, + headers: { + Location: new URL('/deny', url.origin).toString() + } + }) + } return new Response(null, { status: httpStatus.FOUND, diff --git a/id.saladeaula.digital/client/app/routes/deny.tsx b/id.saladeaula.digital/client/app/routes/deny.tsx new file mode 100644 index 0000000..e59263d --- /dev/null +++ b/id.saladeaula.digital/client/app/routes/deny.tsx @@ -0,0 +1,20 @@ +import { LockIcon } from 'lucide-react' +import type { Route } from './+types' + +export function meta({}: Route.MetaArgs) { + return [{ title: 'Acesso negado' }] +} + +export default function Deny({}: Route.ComponentProps) { + return ( + <> +
+ +
+

Acesso negado.

+

Você não tem permissão.

+
+
+ + ) +} diff --git a/id.saladeaula.digital/client/app/routes/revoke.ts b/id.saladeaula.digital/client/app/routes/revoke.ts index d0807da..de21588 100644 --- a/id.saladeaula.digital/client/app/routes/revoke.ts +++ b/id.saladeaula.digital/client/app/routes/revoke.ts @@ -8,8 +8,6 @@ export async function action({ request, context }: Route.ActionArgs) { body: await request.text() }) - // console.log(await r.text(), r) - return new Response(await r.text(), { status: r.status, headers: r.headers diff --git a/id.saladeaula.digital/tests/routes/test_authorize.py b/id.saladeaula.digital/tests/routes/test_authorize.py index 8983fe8..8506364 100644 --- a/id.saladeaula.digital/tests/routes/test_authorize.py +++ b/id.saladeaula.digital/tests/routes/test_authorize.py @@ -53,14 +53,14 @@ def test_authorize( assert len(r['items']) == 3 -def test_unauthorized( +def test_forbidden( app, seeds, dynamodb_persistence_layer: DynamoDBPersistenceLayer, http_api_proxy: HttpApiProxy, lambda_context: LambdaContext, ): - session_id = new_session(USER_ID) + session_id = new_session('fd5914ec-fd37-458b-b6b9-8aeab38b666b') r = app.lambda_handler( http_api_proxy( @@ -81,7 +81,7 @@ def test_unauthorized( lambda_context, ) - assert r['statusCode'] == HTTPStatus.UNAUTHORIZED + assert r['statusCode'] == HTTPStatus.FORBIDDEN def test_authorize_revoked( diff --git a/id.saladeaula.digital/tests/seeds.jsonl b/id.saladeaula.digital/tests/seeds.jsonl index 27ced47..2b4fd41 100644 --- a/id.saladeaula.digital/tests/seeds.jsonl +++ b/id.saladeaula.digital/tests/seeds.jsonl @@ -13,5 +13,9 @@ // User data {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "0", "name": "Sérgio R Siqueira", "email": "sergio@somosbeta.com.br"} {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "PASSWORD", "hash": "$pbkdf2-sha256$29000$IuTcm7M2BiAEgPB.b.3dGw$d8xVCbx8zxg7MeQBrOvCOgniiilsIHEMHzoH/OXftLQ"} -{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "SCOPE", "scope": "read:users read:enrollments"} +{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "SCOPE", "scope": "openid profile email offline_access read:users read:courses"} {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "SESSION#36af142e-9f6d-49d3-bfe9-6a6bd6ab2712", "created_at": "2025-09-17T13:44:34.544491-03:00", "ttl": 1760719474} + +{"id": "fd5914ec-fd37-458b-b6b9-8aeab38b666b", "sk": "0", "name": "Johnny Cash", "email": "johnny@johnnycash.com"} +{"id": "fd5914ec-fd37-458b-b6b9-8aeab38b666b", "sk": "PASSWORD", "hash": "$pbkdf2-sha256$29000$IuTcm7M2BiAEgPB.b.3dGw$d8xVCbx8zxg7MeQBrOvCOgniiilsIHEMHzoH/OXftLQ"} +{"id": "fd5914ec-fd37-458b-b6b9-8aeab38b666b", "sk": "SCOPE", "scope": "openid"}