OAuth security hardening: RFC compliance, PKCE binding, rate limiting (#18305)
## Summary Follow-up to #18267. Hardens the OAuth implementation with security fixes identified during audit: **P0 — Critical:** - Bind authorization codes to `client_id` in context to prevent auth code injection (RFC 6749 §4.1.3) - Store PKCE `code_challenge` directly in auth code context instead of a separate `CodeChallenge` token — cryptographically binds the challenge to its code - Enforce `code_verifier` when `code_challenge` was used during authorization - Hash authorization codes (SHA-256) before storage to prevent exposure if DB is compromised - Add `Cache-Control: no-store` + `Pragma: no-cache` headers on token responses (RFC 6749 §5.1) - Add rate limiting on `/oauth/token` endpoint (20 req/min per client via existing `ThrottlerService`) **P1 — High:** - Return HTTP 401 for `invalid_client` errors instead of 400 (RFC 6749 §5.2) - Verify refresh tokens belong to the presenting client (cross-client token theft prevention) - Limit fields exposed by public `findApplicationRegistrationByClientId` query to only what the frontend needs (`id`, `name`, `logoUrl`, `websiteUrl`, `oAuthScopes`) - Require `API_KEYS_AND_WEBHOOKS` permission for `createApplicationRegistration` mutation **P2/P3 — Medium/Low:** - Add error handling and loading states to frontend Authorize page - Rename redirect URL param from `authorizationCode` to `code` (RFC standard) - Add unit tests for `validateRedirectUri` utility (8 test cases) ## Test plan - [ ] Existing OAuth integration tests updated for all changes (hashed codes, context-based PKCE, client binding, 401 status codes, cache headers) - [ ] New test: auth code rejected when presented by a different client - [ ] New test: refresh token rejected when presented by a different client - [ ] New test: `code_verifier` required when PKCE was used in authorization - [ ] New test: `Cache-Control: no-store` header present on responses - [ ] New unit tests for `validateRedirectUri` (HTTPS, localhost, fragments, invalid URIs) - [ ] Verify frontend authorize page shows errors gracefully Made with [Cursor](https://cursor.com)
This commit is contained in:
+46
@@ -0,0 +1,46 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddWorkspaceIdToApplicationRegistration1772267875869
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddWorkspaceIdToApplicationRegistration1772267875869';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."applicationRegistration" ADD "workspaceId" uuid`,
|
||||
);
|
||||
|
||||
// Delete any orphaned registrations that can't be assigned a workspace
|
||||
await queryRunner.query(`
|
||||
DELETE FROM "core"."applicationRegistration"
|
||||
WHERE "workspaceId" IS NULL
|
||||
`);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."applicationRegistration" ALTER COLUMN "workspaceId" SET NOT NULL`,
|
||||
);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX "IDX_APPLICATION_REGISTRATION_WORKSPACE_ID"
|
||||
ON "core"."applicationRegistration" ("workspaceId")
|
||||
`);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."applicationRegistration" ADD CONSTRAINT "FK_94ab20372e448d45088357f884e" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT "FK_94ab20372e448d45088357f884e"`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_APPLICATION_REGISTRATION_WORKSPACE_ID"`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."applicationRegistration" DROP COLUMN "workspaceId"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user