Skip to content

Commit

Permalink
feat: Keycloak Backend Integration (#179)
Browse files Browse the repository at this point in the history
* chore: Update to @mean-stream/nestx v0.12
* feat(backend): Set up auth
* feat(backend): Save creator user in polls
* fix(backend): Add missing poll properties
* feat(backend): Save user in participants
* chore: Fix lint problems

---------

Co-authored-by: Julian Holfeld <morphclue@gmail.com>
Co-authored-by: Clashsoft <Clashsoft@users.noreply.github.com>
  • Loading branch information
3 people authored Jul 22, 2024
1 parent 30d8aa2 commit 077c1ff
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 33 deletions.
4 changes: 4 additions & 0 deletions apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {AuthModule} from '@mean-stream/nestx/auth';
import {Module} from '@nestjs/common';
import {MongooseModule} from '@nestjs/mongoose';

import {OptionalAuthGuard} from './auth/optional-auth.guard';
import {environment} from './environment';
import {ImprintModule} from './imprint/imprint.module';
import {MailModule} from './mail/mail.module';
Expand All @@ -12,13 +14,15 @@ import {TokenModule} from './token/token.module';
@Module({
imports: [
MongooseModule.forRoot(environment.mongo.uri),
AuthModule.forRoot(environment.auth),
PollModule,
TokenModule,
StatisticsModule,
MailModule,
PushModule,
ImprintModule,
],
providers: [OptionalAuthGuard],
})
export class AppModule {
}
12 changes: 12 additions & 0 deletions apps/backend/src/auth/optional-auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {Injectable} from '@nestjs/common';
import {AuthGuard} from '@nestjs/passport';

@Injectable()
export class OptionalAuthGuard extends AuthGuard('jwt') {
handleRequest(err: any, user: any) {
if (err) {
throw err; // the default implementation checks if user is falsy and throws an UnauthorizedException
}
return user;
}
}
14 changes: 13 additions & 1 deletion apps/backend/src/environment.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {AuthModuleOptions} from '@mean-stream/nestx/auth';

export const environment = {
port: +process.env.PORT || 3000,
origin: process.env.ORIGIN || 'http://localhost:4200',
Expand All @@ -7,5 +9,15 @@ export const environment = {
assetPath: __dirname + '/assets/',
polls: {
activeDays: +process.env.ACTIVE_DAYS || 7, // how many days a poll is active after the deadline is reached
}
},
auth: {
publicKey: `-----BEGIN PUBLIC KEY-----\n${
// see http://localhost:8080/auth/admin/master/console/#/apollusia/realm-settings/keys/list -> RS256 -> Public Key
process.env.AUTH_PUBLIC_KEY || 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6RcQMrMgYacLZV2S6HXuPfbo6dF4y8ZjqrkMEM+Nj2Jt96BC4Ss9O9EkhQonXy2FWcUxvmEKicRApAMXto7r6TiE/LCi+pLfMnAxcIsiQIqDZrVWn5y1jz8gdKuvPxsTW1q3XEjqWuYUZof3/jahBrr1BBtNYRMonOC50a7d8wg7IBxwC3kk9zh8MImfJw6BZmSQ8fNuoHCORsUht7HW1h+HtbFEQEcTVUPxXUvV7Mw6Bgm7oWJLA3gG3heuOyUapqKnGkkMaJrA2xGrW9/66I65UD8jZgoEv5n2uGwXsmAafxZfoA+Mx/xCY6zEhpbxedtp99M2k0pvitwZmXCsnwIDAQAB'
}\n-----END PUBLIC KEY-----`,
verifyOptions: {
algorithms: (process.env.AUTH_ALGORITHMS || 'RS256').split(',') as any[],
issuer: process.env.AUTH_ISSUER || 'http://localhost:8080/auth/realms/apollusia',
},
} satisfies AuthModuleOptions,
};
25 changes: 19 additions & 6 deletions apps/backend/src/poll/poll/poll.controller.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import {
CreateParticipantDto,
MailDto,
Participant,
CreateParticipantDto,
PollDto,
PollEvent,
PollEventDto,
ReadParticipantDto,
ReadPollDto,
ReadPollEventDto,
ReadStatsPollDto,
UpdateParticipantDto, ReadPollEventDto,
UpdateParticipantDto,
} from '@apollusia/types';
import {AuthUser, UserToken} from '@mean-stream/nestx/auth';
import {ObjectIdPipe} from '@mean-stream/nestx/ref';
import {
Body,
Expand All @@ -25,10 +27,12 @@ import {
Post,
Put,
Query,
UseGuards,
} from '@nestjs/common';
import {Types} from 'mongoose';

import {PollService} from './poll.service';
import {OptionalAuthGuard} from '../../auth/optional-auth.guard';


@Controller('poll')
Expand Down Expand Up @@ -59,8 +63,12 @@ export class PollController {
}

@Post()
async postPoll(@Body() pollDto: PollDto): Promise<ReadPollDto> {
return this.pollService.postPoll(pollDto);
@UseGuards(OptionalAuthGuard)
async postPoll(
@Body() pollDto: PollDto,
@AuthUser() user?: UserToken,
): Promise<ReadPollDto> {
return this.pollService.postPoll(pollDto, user);
}

@Put(':id')
Expand Down Expand Up @@ -120,8 +128,13 @@ export class PollController {
}

@Post(':id/participate')
async postParticipation(@Param('id', ObjectIdPipe) id: Types.ObjectId, @Body() participant: CreateParticipantDto): Promise<Participant> {
return this.pollService.postParticipation(id, participant);
@UseGuards(OptionalAuthGuard)
async postParticipation(
@Param('id', ObjectIdPipe) id: Types.ObjectId,
@Body() participant: CreateParticipantDto,
@AuthUser() user?: UserToken,
): Promise<Participant> {
return this.pollService.postParticipation(id, participant, user);
}

@Put('mail/participate')
Expand Down
8 changes: 5 additions & 3 deletions apps/backend/src/poll/poll/poll.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
UpdateParticipantDto,
} from '@apollusia/types';
import {Doc} from '@mean-stream/nestx';
import {UserToken} from '@mean-stream/nestx/auth';
import {Injectable, Logger, NotFoundException, OnModuleInit, UnprocessableEntityException} from '@nestjs/common';
import {InjectModel} from '@nestjs/mongoose';
import {Document, FilterQuery, Model, Types} from 'mongoose';
Expand Down Expand Up @@ -184,8 +185,8 @@ export class PollService implements OnModuleInit {
.exec();
}

async postPoll(pollDto: PollDto): Promise<ReadPollDto> {
const poll = await this.pollModel.create(pollDto);
async postPoll(pollDto: PollDto, user?: UserToken): Promise<ReadPollDto> {
const poll = await this.pollModel.create(user ? {...pollDto, createdBy: user.sub} : pollDto);
return this.mask(poll.toObject());
}

Expand Down Expand Up @@ -311,7 +312,7 @@ export class PollService implements OnModuleInit {
return this.participantModel.find({poll}).exec();
}

async postParticipation(id: Types.ObjectId, dto: CreateParticipantDto): Promise<Participant> {
async postParticipation(id: Types.ObjectId, dto: CreateParticipantDto, user?: UserToken): Promise<Participant> {
const poll = await this.pollModel.findById(id).exec();
if (!poll) {
throw new NotFoundException(id);
Expand All @@ -326,6 +327,7 @@ export class PollService implements OnModuleInit {
const participant = await this.participantModel.create({
...dto,
poll: new Types.ObjectId(id),
createdBy: user?.sub,
});

poll.adminMail && this.sendAdminInfo(poll, participant);
Expand Down
10 changes: 8 additions & 2 deletions libs/types/src/lib/schema/participant.schema.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {Ref} from '@mean-stream/nestx/ref';
import {Prop, Schema, SchemaFactory} from '@nestjs/mongoose';
import {ApiProperty, ApiPropertyOptional} from '@nestjs/swagger';
import {IsEmail, IsNotEmpty, IsObject, IsOptional, IsString, MinLength} from 'class-validator';
import {Types} from 'mongoose';
import {IsEmail, IsNotEmpty, IsObject, IsOptional, IsString, IsUUID, MinLength} from 'class-validator';
import {SchemaTypes, Types} from 'mongoose';

import {Poll} from './poll.schema';

Expand All @@ -23,6 +23,12 @@ export class Participant {
@ApiPropertyOptional()
updatedAt?: Date;

@ApiProperty({format: 'uuid'})
@Prop({required: false, type: SchemaTypes.UUID, transform: (v: object) => v.toString()})
@IsOptional()
@IsUUID()
createdBy?: string;

@Ref(Poll.name, {index: 1})
poll: Types.ObjectId;

Expand Down
27 changes: 24 additions & 3 deletions libs/types/src/lib/schema/poll.schema.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import 'base64-js'; // FIXME needs explicit import because nx does not detect it in objectIdToBase64
import {objectIdToBase64, RefArray} from '@mean-stream/nestx/ref';
import {objectIdToBase64} from '@mean-stream/nestx/ref';
import {Prop, Schema, SchemaFactory} from '@nestjs/mongoose';
import {ApiProperty, ApiPropertyOptional} from '@nestjs/swagger';
import {Type} from 'class-transformer';
import {IsNotEmpty, IsObject, IsOptional, IsString, IsTimeZone, MinLength, ValidateNested} from 'class-validator';
import {Types} from 'mongoose';
import {
IsNotEmpty,
IsObject,
IsOptional,
IsString,
IsTimeZone,
IsUUID,
MinLength,
ValidateNested,
} from 'class-validator';
import {SchemaTypes, Types} from 'mongoose';
import {Settings, SettingsSchema} from './settings';

@Schema({
Expand Down Expand Up @@ -44,6 +53,18 @@ export class Poll {
@ApiProperty()
id: string;

@ApiPropertyOptional()
createdAt?: Date;

@ApiPropertyOptional()
updatedAt?: Date;

@ApiProperty({format: 'uuid'})
@Prop({required: false, type: SchemaTypes.UUID, transform: (v: object) => v.toString()})
@IsOptional()
@IsUUID()
createdBy?: string;

@Prop({required: true})
@ApiProperty()
@IsString()
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,18 @@
"@angular/router": "~17.3.9",
"@angular/service-worker": "~17.3.9",
"@angular/ssr": "~17.3.7",
"@mean-stream/nestx": "^0.11.4",
"@mean-stream/nestx": "^0.12.0",
"@mean-stream/ngbx": "^0.13.0",
"@nestjs-modules/mailer": "^2.0.2",
"@nestjs/common": "^10.3.9",
"@nestjs/config": "^3.2.2",
"@nestjs/core": "^10.3.9",
"@nestjs/jwt": "^10.2.0",
"@nestjs/mongoose": "^10.0.6",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.3.9",
"@nestjs/swagger": "^7.3.1",
"@nestjs/websockets": "^10.3.10",
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
"angular-calendar": "^0.31.1",
"angularx-flatpickr": "^7.3.0",
Expand All @@ -115,6 +118,7 @@
"mongoose": "^8.4.3",
"ngx-cookie-service-ssr": "^17.1.0",
"ngx-countup": "^13.1.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.1.14",
"rxjs": "~7.8.1",
"tslib": "^2.6.3",
Expand Down
Loading

0 comments on commit 077c1ff

Please sign in to comment.