Files
immich/server/src/controllers/oauth.controller.ts

110 lines
3.5 KiB
TypeScript
Raw Normal View History

import { Body, Controller, Get, HttpCode, HttpStatus, Post, Redirect, Req, Res } from '@nestjs/common';
2025-11-11 17:01:14 -05:00
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Request, Response } from 'express';
import {
AuthDto,
LoginResponseDto,
OAuthAuthorizeResponseDto,
OAuthCallbackDto,
OAuthConfigDto,
} from 'src/dtos/auth.dto';
import { UserAdminResponseDto } from 'src/dtos/user.dto';
2025-11-11 17:01:14 -05:00
import { ApiTag, AuthType, ImmichCookie } from 'src/enum';
import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
import { AuthService, LoginDetails } from 'src/services/auth.service';
2024-04-19 11:19:23 -04:00
import { respondWithCookie } from 'src/utils/response';
2025-11-11 17:01:14 -05:00
@ApiTags(ApiTag.Authentication)
@Controller('oauth')
export class OAuthController {
constructor(private service: AuthService) {}
@Get('mobile-redirect')
@Redirect()
2025-11-11 17:01:14 -05:00
@ApiOperation({
summary: 'Redirect OAuth to mobile',
description:
'Requests to this URL are automatically forwarded to the mobile app, and is used in some cases for OAuth redirecting.',
})
redirectOAuthToMobile(@Req() request: Request) {
return {
url: this.service.getMobileRedirect(request.url),
statusCode: HttpStatus.TEMPORARY_REDIRECT,
};
}
@Post('authorize')
2025-11-11 17:01:14 -05:00
@ApiOperation({
summary: 'Start OAuth',
description: 'Initiate the OAuth authorization process.',
})
async startOAuth(
@Body() dto: OAuthConfigDto,
@Res({ passthrough: true }) res: Response,
@GetLoginDetails() loginDetails: LoginDetails,
): Promise<OAuthAuthorizeResponseDto> {
const { url, state, codeVerifier } = await this.service.authorize(dto);
return respondWithCookie(
res,
{ url },
{
isSecure: loginDetails.isSecure,
values: [
2025-07-15 14:50:13 -04:00
{ key: ImmichCookie.OAuthState, value: state },
{ key: ImmichCookie.OAuthCodeVerifier, value: codeVerifier },
],
},
);
}
@Post('callback')
2025-11-11 17:01:14 -05:00
@ApiOperation({
summary: 'Finish OAuth',
description: 'Complete the OAuth authorization process by exchanging the authorization code for a session token.',
})
async finishOAuth(
@Req() request: Request,
@Res({ passthrough: true }) res: Response,
@Body() dto: OAuthCallbackDto,
@GetLoginDetails() loginDetails: LoginDetails,
): Promise<LoginResponseDto> {
const body = await this.service.callback(dto, request.headers, loginDetails);
2025-07-15 14:50:13 -04:00
res.clearCookie(ImmichCookie.OAuthState);
res.clearCookie(ImmichCookie.OAuthCodeVerifier);
2024-04-19 11:19:23 -04:00
return respondWithCookie(res, body, {
isSecure: loginDetails.isSecure,
values: [
2025-07-15 14:50:13 -04:00
{ key: ImmichCookie.AccessToken, value: body.accessToken },
{ key: ImmichCookie.AuthType, value: AuthType.OAuth },
{ key: ImmichCookie.IsAuthenticated, value: 'true' },
2024-04-19 11:19:23 -04:00
],
});
}
@Post('link')
@Authenticated()
@HttpCode(HttpStatus.OK)
2025-11-11 17:01:14 -05:00
@ApiOperation({
summary: 'Link OAuth account',
description: 'Link an OAuth account to the authenticated user.',
})
linkOAuthAccount(
@Req() request: Request,
@Auth() auth: AuthDto,
@Body() dto: OAuthCallbackDto,
): Promise<UserAdminResponseDto> {
return this.service.link(auth, dto, request.headers);
}
@Post('unlink')
@Authenticated()
@HttpCode(HttpStatus.OK)
2025-11-11 17:01:14 -05:00
@ApiOperation({
summary: 'Unlink OAuth account',
description: 'Unlink the OAuth account from the authenticated user.',
})
unlinkOAuthAccount(@Auth() auth: AuthDto): Promise<UserAdminResponseDto> {
return this.service.unlink(auth);
}
}