Skip to content

Commit 61a932f

Browse files
ThyMinimalDevdevin-ai-integration[bot]cubic-dev-ai[bot]PeerRichvolnei
authored
feat: OAuth2 controller api v2 + refactor oAuth Trpc handlers (calcom#25989)
* feat(api-v2): add OAuth2 controller skeleton for auth module - Add new OAuth2 module under /auth with controller, service, repository, and DTOs - Implement skeleton endpoints: - GET /v2/auth/oauth2/clients/:clientId - Get OAuth client info - POST /v2/auth/oauth2/clients/:clientId/authorize - Generate authorization code - POST /v2/auth/oauth2/clients/:clientId/exchange - Exchange code for tokens - POST /v2/auth/oauth2/clients/:clientId/refresh - Refresh access token - Create input DTOs for authorize, exchange, and refresh operations - Create output DTOs for client info, authorization code, and tokens - Register OAuth2Module in endpoints.module.ts Co-Authored-By: morgan@cal.com <morgan@cal.com> * chore: add missing functions to platform libraries * feat(api-v2): integrate OAuthService from platform-libraries into OAuth2 controller - Add OAuthService export to platform-libraries - Refactor OAuth2Service to use OAuthService.validateClient() and OAuthService.verifyPKCE() - Remove duplicate local implementations of validateClient and verifyPKCE methods Co-Authored-By: morgan@cal.com <morgan@cal.com> * feat(api-v2): use local validateClient and verifyPKCE implementations - Remove OAuthService export from platform-libraries (will be deprecated) - Reimplement validateClient and verifyPKCE methods locally in OAuth2Service - Use verifyCodeChallenge and generateSecret from platform-libraries directly Co-Authored-By: morgan@cal.com <morgan@cal.com> * refactor(api-v2): separate repositories for OAuth2, access codes, and teams - Create AccessCodeRepository for access code Prisma operations - Add findTeamBySlugWithAdminRole method to TeamsRepository - Move generateAuthorizationCode, createTokens, verifyRefreshToken to OAuth2Service - Update OAuth2Repository to only contain OAuth client operations - Update OAuth2Module to import TeamsModule and provide AccessCodeRepository Addresses PR review comments to properly separate concerns Co-Authored-By: morgan@cal.com <morgan@cal.com> * feat(api-v2): implement JWT token creation and verification for OAuth2 - Implement createTokens method using jsonwebtoken to sign access and refresh tokens - Implement verifyRefreshToken method to verify JWT refresh tokens - Add ConfigService injection for CALENDSO_ENCRYPTION_KEY access - Import ConfigModule in OAuth2Module for dependency injection Based on logic from apps/web/app/api/auth/oauth/token/route.ts and apps/web/app/api/auth/oauth/refreshToken/route.ts Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: refactor and dayjs import fix * feat(api-v2): add ApiAuthGuard to getClient endpoint and e2e tests for OAuth2 - Add @UseGuards(ApiAuthGuard) to getClient endpoint for authentication - Add comprehensive e2e tests for all OAuth2 endpoints: - GET /v2/auth/oauth2/clients/:clientId - POST /v2/auth/oauth2/clients/:clientId/authorize - POST /v2/auth/oauth2/clients/:clientId/exchange - POST /v2/auth/oauth2/clients/:clientId/refresh - Tests cover happy paths and error cases (invalid client, invalid code, etc.) Co-Authored-By: morgan@cal.com <morgan@cal.com> * chore(api-v2): hide OAuth2 endpoints from Swagger documentation Co-Authored-By: morgan@cal.com <morgan@cal.com> * hide endpoints from doc * fix(api-v2): address PR feedback for OAuth2 controller - Fix P0 critical bug: token_type field name mismatch in DecodedRefreshToken interface - Add @equals('authorization_code') validation to exchange.input.ts grantType - Add @equals('refresh_token') validation to refresh.input.ts grantType - Add @isnotempty() validation to get-client.input.ts clientId - Add @expose() decorators to all output DTOs for proper serialization - Add security test assertion to verify clientSecret is not returned in response Co-Authored-By: morgan@cal.com <morgan@cal.com> * feat(api-v2): implement redirect behavior for OAuth2 authorize endpoint - Update authorize endpoint to return HTTP 303 redirect with authorization code - Add exact match validation for redirect URI (security requirement) - Implement error handling with redirect to redirect URI and error query params - Add state parameter support for CSRF protection - Update e2e tests to verify redirect behavior (303 status, Location header, error redirects) Co-Authored-By: morgan@cal.com <morgan@cal.com> * refactor(api-v2): address PR feedback for OAuth2 authorize endpoint - Make redirectUri a required input parameter (remove optional fallback) - Move buildRedirectUrl and mapErrorToOAuthError to oauth2Service - Add return statements to res.redirect() calls - Simplify controller by delegating redirect URL building to service Co-Authored-By: morgan@cal.com <morgan@cal.com> * wip move code outside of api v2 * feat(oauth): wire up dependency injection for OAuthService and repositories Co-Authored-By: morgan@cal.com <morgan@cal.com> * refactor: oAuthService used in routes * fix imports * fix(api-v2): return 404 for invalid client ID instead of redirect in authorize endpoint Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix(api-v2): fix E2E test URL paths and remove bootstrap() in authenticated section - Remove bootstrap() call in authenticated section to match working test pattern - Change URL paths from /api/v2/... to /v2/... in authenticated section - Keep bootstrap() and /api/v2/... paths in unauthenticated section Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix(api-v2): mock getToken from next-auth/jwt for E2E tests - Add jest.mock for next-auth/jwt getToken function - Mock returns null for unauthenticated tests - Mock returns { email: userEmail } for authenticated tests - Remove withNextAuth helper since ApiAuthGuard uses ApiAuthStrategy which calls getToken directly, not NextAuthStrategy Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix tests and code review * cleanup generate secrets * cleanup controller * chore: remove console.log from OAuthService.validateClient Co-Authored-By: morgan@cal.com <morgan@cal.com> * Update apps/web/app/api/auth/oauth/refreshToken/route.ts Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * fix(api-v2): add bootstrap() to authenticated E2E tests and update URL paths to /api/v2/ Co-Authored-By: morgan@cal.com <morgan@cal.com> * Merge remote-tracking branch 'origin/devin/oauth2-controller-skeleton-1765988792' and remove console.log from token route Co-Authored-By: morgan@cal.com <morgan@cal.com> * chore: remove dead code * refactor * refactor: generateAuthCode trpc handler and getClient trpc handler * remove pkce check for refreshToken endpoint * Update packages/trpc/server/routers/viewer/oAuth/getClient.handler.ts Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * refactor: error handling * refactor: error handling * refactor: error handling * remove console log * provide redirectUri in generateAuthCodeHandler * provide redirectUri in authorize view * refactor: replace HttpError with ErrorWithCode in OAuth files - Update OAuthService to use ErrorWithCode instead of HttpError - Update mapErrorToOAuthError to check ErrorCode instead of status codes - Update token/route.ts and refreshToken/route.ts to use ErrorWithCode - Add ErrorWithCode export to platform-libraries - Use getHttpStatusCode to map ErrorCode to HTTP status codes Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: update generateAuthCode handler to use ErrorWithCode instead of HttpError The handler was still checking for HttpError in its catch block, but OAuthService now throws ErrorWithCode. This caused the error messages to be lost and replaced with 'server_error' instead of the specific error messages. Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: update OAuth2 controller to use ErrorWithCode instead of HttpError The controller was still checking for HttpError in its catch blocks, but OAuthService now throws ErrorWithCode. This caused all errors to return 500 Internal Server Error instead of the correct HTTP status codes (400, 401, 404). Also added getHttpStatusCode export to platform-libraries. Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: add explicit unknown type annotation to catch blocks in OAuth2 controller Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: change NotFoundException to HttpException in authorize endpoint Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: handle err with ErrorWithCode in authorize endpoint catch block Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: move errorWithCode * fix: error code rfc * fix: no need to handle errors there is a middleware * refactor errors * refactor errors * refactor redirecturi and state from api * refactor: address PR comments for OAuth2 controller - Remove unused GetOAuth2ClientInput class - Change API tag from 'Auth / OAuth2' to 'OAuth2' - Move OAuthClientFixture to separate fixture file (oauth2-client.repository.fixture.ts) - Add 'type' property to OAuth2ClientDto for confidential/public client type - Rename 'clientId' to 'id' in OAuth2ClientDto (using @expose mapping) - Clean up duplicate provider declarations in oauth2.module.ts - Update e2e tests to use new fixture and verify type property Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Volnei Munhoz <volnei.munhoz@gmail.com> Co-authored-by: Volnei Munhoz <volnei@cal.com>
1 parent ca32f04 commit 61a932f

44 files changed

Lines changed: 1677 additions & 360 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,15 @@ import { X_CAL_CLIENT_ID, X_CAL_PLATFORM_EMBED } from "@calcom/platform-constant
5858
import { BOOKING_READ, SUCCESS_STATUS, BOOKING_WRITE } from "@calcom/platform-constants";
5959
import {
6060
BookingResponse,
61-
HttpError,
6261
handleMarkNoShow,
6362
getAllUserBookings,
6463
getBookingInfo,
6564
handleCancelBooking,
6665
getBookingForReschedule,
67-
ErrorCode,
6866
} from "@calcom/platform-libraries";
6967
import { CreationSource } from "@calcom/platform-libraries";
7068
import { type InstantBookingCreateResult } from "@calcom/platform-libraries/bookings";
69+
import { HttpError, ErrorCode } from "@calcom/platform-libraries/errors";
7170
import {
7271
GetBookingsInput_2024_04_15,
7372
CancelBookingInput_2024_04_15,
@@ -125,7 +124,7 @@ export class BookingsController_2024_04_15 {
125124
private readonly instantBookingCreateService: InstantBookingCreateService,
126125
private readonly eventTypeRepository: PrismaEventTypeRepository,
127126
private readonly teamRepository: PrismaTeamRepository
128-
) { }
127+
) {}
129128

130129
@Get("/")
131130
@UseGuards(ApiAuthGuard)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { PrismaAccessCodeRepository } from "@/lib/repositories/prisma-access-code.repository";
2+
import { PrismaOAuthClientRepository } from "@/lib/repositories/prisma-oauth-client.repository";
3+
import { PrismaTeamRepository } from "@/lib/repositories/prisma-team.repository";
4+
import { OAuthService } from "@/lib/services/oauth.service";
5+
import { PrismaModule } from "@/modules/prisma/prisma.module";
6+
import { Module } from "@nestjs/common";
7+
8+
@Module({
9+
imports: [PrismaModule],
10+
providers: [PrismaAccessCodeRepository, PrismaOAuthClientRepository, PrismaTeamRepository, OAuthService],
11+
exports: [OAuthService],
12+
})
13+
export class oAuthServiceModule {}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
2+
import { Injectable } from "@nestjs/common";
3+
4+
import { PrismaAccessCodeRepository as PrismaAccessCodeRepositoryLib } from "@calcom/platform-libraries/repositories";
5+
import type { PrismaClient } from "@calcom/prisma";
6+
7+
@Injectable()
8+
export class PrismaAccessCodeRepository extends PrismaAccessCodeRepositoryLib {
9+
constructor(private readonly dbWrite: PrismaWriteService) {
10+
super(dbWrite.prisma as unknown as PrismaClient);
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
2+
import { Injectable } from "@nestjs/common";
3+
4+
import { PrismaOAuthClientRepository as PrismaOAuthClientRepositoryLib } from "@calcom/platform-libraries/repositories";
5+
import type { PrismaClient } from "@calcom/prisma";
6+
7+
@Injectable()
8+
export class PrismaOAuthClientRepository extends PrismaOAuthClientRepositoryLib {
9+
constructor(private readonly dbWrite: PrismaWriteService) {
10+
super(dbWrite.prisma as unknown as PrismaClient);
11+
}
12+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { PrismaAccessCodeRepository } from "@/lib/repositories/prisma-access-code.repository";
2+
import { PrismaOAuthClientRepository } from "@/lib/repositories/prisma-oauth-client.repository";
3+
import { PrismaTeamRepository } from "@/lib/repositories/prisma-team.repository";
4+
import { Injectable } from "@nestjs/common";
5+
6+
import { OAuthService as BaseOAuthService } from "@calcom/platform-libraries";
7+
8+
@Injectable()
9+
export class OAuthService extends BaseOAuthService {
10+
constructor(
11+
accessCodeRepository: PrismaAccessCodeRepository,
12+
oAuthClientRepository: PrismaOAuthClientRepository,
13+
teamsRepository: PrismaTeamRepository
14+
) {
15+
super({
16+
accessCodeRepository: accessCodeRepository,
17+
oAuthClientRepository: oAuthClientRepository,
18+
teamsRepository: teamsRepository,
19+
});
20+
}
21+
}

0 commit comments

Comments
 (0)