Skip to content

Commit

Permalink
feat: oauth secrets api
Browse files Browse the repository at this point in the history
  • Loading branch information
alerdenisov committed Nov 6, 2023
1 parent 4061582 commit 7b1033b
Show file tree
Hide file tree
Showing 21 changed files with 506 additions and 41 deletions.
2 changes: 2 additions & 0 deletions apps/api/src/api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { AuthEmailService } from './auth-email/auth-email.service';
import { AuthEmailModule } from './auth-email/auth-email.module';
import { AuthDiscordModule } from './auth-discord/auth-discord.module';
import { AuthTwitterModule } from './auth-twitter/auth-twitter.module';
import { SecretsModule } from './secrets/secrets.module';

@Module({
imports: [
Expand All @@ -48,6 +49,7 @@ import { AuthTwitterModule } from './auth-twitter/auth-twitter.module';
AdminModule,
DevicesModule,
PiecesModule,
SecretsModule,
],
controllers: [ApiController, SignApiController],
providers: [ApiService, SignApiService, AuthEmailService],
Expand Down
9 changes: 0 additions & 9 deletions apps/api/src/auth-discord/auth-discord.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,6 @@ export class AuthDiscordService {

private async exchangeTokens(code: string, connect: boolean = false) {
const { clientID, clientSecret, callbackURL } = this.configService.get('discord', { infer: true });
console.log({
method: 'exchangeTokens',
clientID,
clientSecret,
callbackURL,
connect
});

const { data } = await firstValueFrom(
this.httpService.request<{
Expand Down Expand Up @@ -70,8 +63,6 @@ export class AuthDiscordService {
throw e;
});

console.log(data);

return {
accessToken: data.access_token,
expiresIn: data.expires_in,
Expand Down
1 change: 0 additions & 1 deletion apps/api/src/auth-twitter/auth-twitter.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ export class AuthTwitterController {
}

private async internalTwitterAuthCallback(body: AuthTwitterLoginDto) {
console.log('uncached auth')
const profile = await this.twitterService.getProfileByCode(body);
const user = await this.userService.getOrCreateTwitterUser(new CreateTwitterUserDto(profile));
const access = this.authService.getJwtAccessToken(user.user);
Expand Down
12 changes: 2 additions & 10 deletions apps/api/src/auth-twitter/auth-twitter.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,15 @@ export class AuthTwitterService {

private async exchangeTokens({code, codeVerifier}: AuthTwitterLoginDto, connect: boolean = false) {
const { clientId, clientSecret, callbackUrl } = this.configService.get('twitter', { infer: true });
console.log({
method: 'exchangeTokens',
clientId,
clientSecret,
callbackUrl,
connect
});

return new TwitterApi({ clientId, clientSecret }).loginWithOAuth2({ code, codeVerifier, redirectUri: callbackUrl + (connect ? '/connect' : '/login') }).catch(e => {
console.log(e)
console.error(e)
throw new HttpException(e, HttpStatus.BAD_REQUEST);
});
}

private async getUserInfo(client: TwitterApi) {
return client.v2.me().catch(e => {
console.log(e)
console.error(e)
throw new HttpException(e, HttpStatus.BAD_REQUEST);
});
}
Expand Down
1 change: 0 additions & 1 deletion apps/api/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ export class AuthController {
@HttpCode(200)
@Post('flows')
async signinFlow(@CurrentUser() userDto: UserContextDto) {
console.log('currentuser', userDto)
const userRequest = {
id: userDto.user.id.toString(),
firstName: userDto.user.firstName,
Expand Down
5 changes: 0 additions & 5 deletions apps/api/src/devices/devices.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,6 @@ export class DevicesService implements OnApplicationBootstrap {
async updateDevice(dto: DeviceUpdateRequestDto, user: UserDto) {
const keys = await this.keyService.getKeysByPublicKeys(dto);

console.log({
keys,
dto,
});

return this.devices.createOrUpdateOne({
...dto,
user,
Expand Down
19 changes: 14 additions & 5 deletions apps/api/src/guards/jwt.guard.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';
import { isObservable, map, Observable } from 'rxjs';
import { isPromise } from 'util/types';

@Injectable()
export class JwtGuard extends AuthGuard('jwt') {
Expand All @@ -14,11 +15,19 @@ export class JwtGuard extends AuthGuard('jwt') {
context.getHandler(),
context.getClass(),
]);

if (isPublic) {
return true;
}
const observableOrPromise = super.canActivate(context);
if (isPromise(observableOrPromise)) {
return observableOrPromise.then((r) => r || true);
}
if (isObservable(observableOrPromise)) {
return observableOrPromise.pipe(map(() => true));
}

return super.canActivate(context);
return observableOrPromise || true;
} else {
return super.canActivate(context);
}
}
}
85 changes: 85 additions & 0 deletions apps/api/src/secrets/secrets.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
Body,
ClassSerializerInterceptor,
Controller,
Get,
HttpCode,
HttpStatus, Patch,
Post,
Query,
Res,
UseInterceptors
} from '@nestjs/common';
import { ApiBadRequestResponse, ApiTags } from '@nestjs/swagger';
import { Response } from 'express';
import { InjectPinoLogger, PinoLogger } from 'nestjs-pino';
import { AnyRoles } from '../decorators/any-role.decorator';
import { CurrentUser } from '../decorators/current-user.decorator';
import { JwtAuth } from '../decorators/jwt-auth.decorator';
import { PublicRoute } from '../decorators/public-route.decorator';
import { UserContextDto } from '../user/user.dto';
import { ClaimConnectionDto, CreateOrUpdateSecretDto, Edition, RefreshConnectionDto } from './secrets.dto';
import { SecretsService } from './secrets.service';

@Controller('api/secrets')
@JwtAuth()
@ApiTags('Secrets')
@UseInterceptors(ClassSerializerInterceptor)
export class SecretsController {
constructor(
@InjectPinoLogger(SecretsController.name) private readonly logger: PinoLogger,
private service: SecretsService) {}
@Get('redirect')
@HttpCode(HttpStatus.OK)
@ApiBadRequestResponse({ description: 'Email already exists' })
async redirect(@Query('code') code: string, @Res() res: Response) {
if (!code) {
return res.send('The code is missing in url')
}

return res.type('html').type('text/html').send(`<script>if(window.opener){window.opener.postMessage({ 'code': '${encodeURIComponent(code)}' },'*')}</script> <html>Redirect succuesfully, this window should close now</html>`)
}

@Get('apps')
@PublicRoute()
@HttpCode(HttpStatus.OK)
async apps(@CurrentUser() user?: UserContextDto, @Query('edition') edition?: Edition) {
return this.service.getClientIds(edition, user?.roles.includes('admin'));
}

@Post('claim')
@PublicRoute()
@HttpCode(HttpStatus.OK)
@ApiBadRequestResponse({ description: 'Email already exists' })
async claim(@Body() dto: ClaimConnectionDto) {
return this.service.claim(dto);
}

@Post('refresh')
@PublicRoute()
@HttpCode(HttpStatus.OK)
@ApiBadRequestResponse({ description: 'Email already exists' })
async refresh(@Body() appConnection: RefreshConnectionDto) {
return this.service.refresh(appConnection);
}

@JwtAuth()
@AnyRoles('admin')
@Post()
@HttpCode(HttpStatus.CREATED)
@ApiBadRequestResponse({ description: 'Secret already exists' })
async create(@Body() dto: CreateOrUpdateSecretDto) {
return this.service.create(dto);
}

@JwtAuth()
@AnyRoles('admin')
@Patch()
@HttpCode(HttpStatus.OK)
@ApiBadRequestResponse({ description: 'Secret already exists' })
async update(@Body() dto: CreateOrUpdateSecretDto) {
return this.service.update(dto);
}


}
84 changes: 84 additions & 0 deletions apps/api/src/secrets/secrets.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsOptional, IsString, IsUrl } from 'class-validator';

export enum Edition {
COMMUNITY = 'ce',
ENTERPRISE = 'ee',
CLOUD = 'cloud',
}

export enum AuthorizationMethod {
HEADER = 'HEADER',
BODY = 'BODY',
}

export class RefreshConnectionDto {
@ApiProperty()
@IsString()
refreshToken: string;

@ApiProperty()
@IsString()
pieceName: string;

@ApiProperty()
@IsString()
clientId: string;

@ApiPropertyOptional()
@IsOptional()
edition?: Edition;

@ApiPropertyOptional()
@IsOptional()
authorizationMethod?: AuthorizationMethod;

@ApiProperty()
@IsUrl()
tokenUrl: string;
}

export class ClaimConnectionDto {
@ApiProperty()
@IsString()
pieceName: string;

@ApiProperty()
@IsString()
code: string;

@ApiProperty()
@IsString()
clientId: string;

@ApiProperty()
@IsUrl()
tokenUrl: string;

@ApiPropertyOptional()
@IsOptional()
@IsString()
codeVerifier?: string;

@ApiPropertyOptional()
@IsOptional()
edition?: Edition;

@ApiPropertyOptional()
@IsOptional()
authorizationMethod?: AuthorizationMethod;
}

export class CreateOrUpdateSecretDto {
@ApiProperty()
@IsString()
pieceName: string;

@ApiProperty()
@IsString()
clientSecret: string;

@ApiProperty()
@IsString()
clientId: string;
}
16 changes: 16 additions & 0 deletions apps/api/src/secrets/secrets.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmExModule } from '@tookey/database';
import { SecretsRepository } from '@tookey/database';
import { SecretsController } from './secrets.controller';
import { SecretsService } from './secrets.service';

const SecretRepositories = TypeOrmExModule.forCustomRepository([SecretsRepository]);

@Module({
imports: [ConfigModule, HttpModule, SecretRepositories],
controllers: [SecretsController],
providers: [SecretsService]
})
export class SecretsModule {}
Loading

0 comments on commit 7b1033b

Please sign in to comment.