Skip to content
This repository has been archived by the owner on Jun 10, 2024. It is now read-only.

Move tokens from memory to DataStax DB #171

Merged
merged 44 commits into from
Aug 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
f7ef3f4
refactor: new Astraservice
Cahllagerfeld Jun 26, 2021
cfb27a3
style: remove log
Cahllagerfeld Jun 26, 2021
15fa97f
style: add js-doc
Cahllagerfeld Jun 27, 2021
33ed254
refactor: configure namespace by header for standup
Cahllagerfeld Jun 27, 2021
de873ad
Merge branch 'main' into issue-137
Cahllagerfeld Jul 13, 2021
26cb288
feat: JWT-Client-Based Authentication
Cahllagerfeld Jul 15, 2021
ef5886d
feat: add scopes guard
Cahllagerfeld Jul 15, 2021
8074056
fix: fixed todos
Cahllagerfeld Jul 16, 2021
47b6c9c
feat: add keyspace interceptor
Cahllagerfeld Jul 17, 2021
281abae
test: update unittests
Cahllagerfeld Jul 17, 2021
2320df9
test: adjust unittests
Cahllagerfeld Jul 17, 2021
f96db25
test: adjust unittests
Cahllagerfeld Jul 17, 2021
21885fa
test: adjust unittests
Cahllagerfeld Jul 17, 2021
cd4c0f2
test: adjust unittests
Cahllagerfeld Jul 17, 2021
513303c
test: update e2e-tests
Cahllagerfeld Jul 18, 2021
a88488c
feat: github-module to new feature
Cahllagerfeld Jul 18, 2021
6319239
feat: add new logic to discord module
Cahllagerfeld Jul 18, 2021
dc17308
feat: add new auth logic to calendar module
Cahllagerfeld Jul 18, 2021
2cddd68
docs: change order for swagger
Cahllagerfeld Jul 18, 2021
cb523e0
test: fix existing e2e tests
Cahllagerfeld Jul 19, 2021
ad61af4
Merge branch 'main' into issue-137
Cahllagerfeld Jul 23, 2021
d0ad64a
fix: add missing catch
Cahllagerfeld Jul 23, 2021
ede725b
Merge pull request #167 from Cahllagerfeld/issue-137c
eddiejaoude Jul 23, 2021
ae18fa7
fix: read/write scopes (#137)
eddiejaoude Jul 23, 2021
8e44321
fix: auth tests (#165)
eddiejaoude Jul 24, 2021
4ee26c9
feat: store tokens in db on register
Cahllagerfeld Jul 25, 2021
a1efc99
feat: validate and delete to database
Cahllagerfeld Jul 25, 2021
b1754a2
style: update tests + lint
Cahllagerfeld Jul 25, 2021
6cefb7a
fix: automated tests for tokens (#165)
eddiejaoude Jul 25, 2021
14dc6b5
feat: token to database
Cahllagerfeld Jul 31, 2021
e3db00e
test: disable delete token test temporarily
Cahllagerfeld Jul 31, 2021
4e08245
test: disable delete token test temporarily
Cahllagerfeld Jul 31, 2021
1ce68b3
Merge branch 'main' into issue-165
Cahllagerfeld Jul 31, 2021
405fabe
feat: get all clients endpoint
Cahllagerfeld Jul 31, 2021
e85cb77
fix: set Guard
Cahllagerfeld Aug 1, 2021
9f53daf
test: fix delete token test
Cahllagerfeld Aug 1, 2021
78c863b
feat: token validation endpoint
Cahllagerfeld Aug 1, 2021
5c2c323
test: add tests for token validation
Cahllagerfeld Aug 1, 2021
261bcf3
style: const instead of let
Cahllagerfeld Aug 1, 2021
3232fe3
test: update auth tests
Cahllagerfeld Aug 2, 2021
1a2e058
feat: add guard to validation endpoint
Cahllagerfeld Aug 2, 2021
a46c5bb
refactor: response handling in controller
Cahllagerfeld Aug 3, 2021
fc167bc
fix: add missing return
Cahllagerfeld Aug 3, 2021
7ca72aa
test: rename step-def
Cahllagerfeld Aug 3, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,4 @@
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
}
5 changes: 5 additions & 0 deletions src/app.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { AstraModule } from '@cahllagerfeld/nestjs-astra';
import { ConfigModule } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AstraConfigService } from './astra/astra-config.service';
import { AuthModule } from './auth/auth.module';

describe('AppController', () => {
Expand All @@ -14,6 +16,9 @@ describe('AppController', () => {
ConfigModule.forRoot({
isGlobal: true,
}),
AstraModule.forRootAsync({
useClass: AstraConfigService,
}),
],
controllers: [AppController],
providers: [AppService],
Expand Down
16 changes: 14 additions & 2 deletions src/auth/auth.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { AstraModule } from '@cahllagerfeld/nestjs-astra';
import { ConfigModule } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { Test, TestingModule } from '@nestjs/testing';
import { AstraConfigService } from '../astra/astra-config.service';
import { AstraService } from '../astra/astra.service';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';

Expand All @@ -8,9 +12,17 @@ describe('AuthController', () => {

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [JwtModule.register({ secret: 'test-secret' })],
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
JwtModule.register({ secret: 'Test' }),
AstraModule.forRootAsync({
useClass: AstraConfigService,
}),
],
controllers: [AuthController],
providers: [AuthService],
providers: [AuthService, AstraService],
}).compile();

controller = module.get<AuthController>(AuthController);
Expand Down
30 changes: 23 additions & 7 deletions src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,40 @@ import {
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Post,
Query,
Res,
UseGuards,
} from '@nestjs/common';
import { ApiQuery, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { ApiParam, ApiQuery, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { Response } from 'express';
import { AuthService } from './auth.service';
import { AuthDTO } from './dto/auth.dto';
import { AuthDTO, TokenValidationDTO } from './dto/auth.dto';
import { TokenGuard } from './token.strategy';

@ApiTags('Auth')
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}

@Get()
@Get('token/:keyspace')
@UseGuards(TokenGuard)
@ApiSecurity('token')
@ApiQuery({ name: 'keyspace', required: true })
getTokens(@Query('keyspace') keyspace: string) {
@ApiParam({ name: 'keyspace', required: true })
getTokens(@Param('keyspace') keyspace: string) {
return this.authService.getClientIds(keyspace);
}

@Post()
@Post('token')
@UseGuards(TokenGuard)
@ApiSecurity('token')
register(@Body() body: AuthDTO) {
return this.authService.register(body);
}

@Delete()
@Delete('token')
@UseGuards(TokenGuard)
@ApiSecurity('token')
@ApiQuery({
Expand All @@ -45,4 +49,16 @@ export class AuthController {
deleteClient(@Query() query) {
return this.authService.removeClient(query.token);
}

@Post('validate')
@UseGuards(TokenGuard)
@ApiSecurity('token')
validateToken(@Body() body: TokenValidationDTO, @Res() response: Response) {
const valid = this.authService.validateToken(body.token);
if (!valid) {
response.status(HttpStatus.BAD_REQUEST).json({ valid });
return;
}
response.status(HttpStatus.OK).json({ valid });
}
}
9 changes: 8 additions & 1 deletion src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AuthService } from './auth.service';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AstraService } from '../astra/astra.service';

@Module({
imports: [
Expand All @@ -17,7 +18,13 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
inject: [ConfigService],
}),
],
providers: [TokenStrategy, ValidationService, AuthService, JwtStrategy],
providers: [
TokenStrategy,
ValidationService,
AuthService,
JwtStrategy,
AstraService,
],
exports: [ValidationService],
controllers: [AuthController],
})
Expand Down
16 changes: 14 additions & 2 deletions src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import { AstraModule } from '@cahllagerfeld/nestjs-astra';
import { ConfigModule } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { Test, TestingModule } from '@nestjs/testing';
import { AstraConfigService } from '../astra/astra-config.service';
import { AstraService } from '../astra/astra.service';
import { AuthService } from './auth.service';

describe('AuthService', () => {
let service: AuthService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [JwtModule.register({ secret: 'test-secret' })],
providers: [AuthService],
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
JwtModule.register({ secret: 'Test' }),
AstraModule.forRootAsync({
useClass: AstraConfigService,
}),
],
providers: [AuthService, AstraService],
}).compile();

service = module.get<AuthService>(AuthService);
Expand Down
101 changes: 66 additions & 35 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,17 @@ import { TokenPayload } from './interfaces/token-payload.interface';
import { JwtService } from '@nestjs/jwt';
import { v4 as uuidv4 } from 'uuid';
import { AuthDTO } from './dto/auth.dto';
import { AstraService } from '../astra/astra.service';
import { Response } from 'express';

@Injectable()
export class AuthService {
//TODO move configCollection to database => own ConfigDataModule
private configCollection: { [id: string]: { knownClients: string[] } } = {};
constructor(private readonly jwtService: JwtService) {}
constructor(
private readonly jwtService: JwtService,
private readonly astraService: AstraService,
) {}

public getClientIds(keyspace: string) {
if (!this.configCollection[keyspace]) return {};
return { tokens: this.configCollection[keyspace]?.knownClients };
}

public register(body: AuthDTO) {
const tokenType = 'bearer';
public async register(body: AuthDTO) {
const clientId = uuidv4();
const { serverId, scopes } = body;

Expand All @@ -25,47 +22,81 @@ export class AuthService {
keyspace: serverId,
scopes,
};
if (!this.configCollection[serverId]) {
this.configCollection[serverId] = { knownClients: [] };
try {
await this.astraService
.create({ clientId }, serverId, 'tokens', clientId)
.toPromise();
} catch (error) {
throw new HttpException(
"Token coundn't be created in the database",
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
this.configCollection[serverId].knownClients = [
...this.configCollection[serverId].knownClients,
clientId,
];

//TODO token-expiry
const signedToken = this.jwtService.sign(payload, { expiresIn: '1y' });
const decoded: any = this.jwtService.decode(signedToken);
const expiresIn: number = decoded.exp - Math.round(Date.now() / 1000);
return { ...payload, accessToken: signedToken, expiresIn, tokenType };
return { ...payload, accessToken: signedToken, expiresIn };
}

public validateClient(payload: TokenPayload): boolean {
public async validateClient(payload: TokenPayload): Promise<boolean> {
const { keyspace, clientId } = payload;
if (
this.configCollection[keyspace] &&
this.configCollection[keyspace].knownClients.includes(clientId)
) {
return true;
let token: string = null;
try {
token = await this.astraService
.get<string>(clientId, keyspace, 'tokens')
.toPromise();
} catch {
return false;
}
if (!token) {
return false;
}
return false;
return true;
}

public removeClient(token: string) {
public async removeClient(token: string) {
let deleteSuccess = false;
let clientId: string;
let keyspace: string;

if (!token)
throw new HttpException('Please provide token', HttpStatus.BAD_REQUEST);
try {
({ clientId, keyspace } = this.jwtService.verify<TokenPayload>(token));
} catch (error) {
throw new HttpException(
"Token couldn't be verified",
HttpStatus.BAD_REQUEST,
);
}
try {
await this.astraService.delete(clientId, keyspace, 'tokens').toPromise();
deleteSuccess = true;
} catch (error) {
throw new Error("Token couldn't be deleted");
}

const decoded = this.jwtService.decode(token) as TokenPayload;
return deleteSuccess;
}

public async getClientIds(keyspace: string) {
let clients;
try {
this.configCollection[
decoded.keyspace
].knownClients = this.configCollection[
decoded.keyspace
].knownClients.filter((client) => client !== decoded.clientId);
console.log(this.configCollection);
return;
} catch (e) {
throw new HttpException('Invalid client id', HttpStatus.BAD_REQUEST);
clients = await this.astraService.find(keyspace, 'tokens').toPromise();
} catch (error) {
return { clients: [] };
}
return { clients: Object.keys(clients) };
}

public validateToken(token: string) {
try {
this.jwtService.verify(token);
return true;
} catch (error) {
return false;
}
}
}
7 changes: 7 additions & 0 deletions src/auth/dto/auth.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,10 @@ export class AuthDTO {
@ApiProperty({ required: true })
serverId: string;
}

export class TokenValidationDTO {
@IsNotEmpty()
@IsString()
@ApiProperty({ required: true })
token: string;
}
1 change: 1 addition & 0 deletions src/auth/token.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export class TokenStrategy extends PassportStrategy(
super({
tokenHeader: 'Client-Token',
tokenQuery: 'Client-Token',
tokenField: 'Client-Token',
passReqToCallback: true,
});
}
Expand Down
Loading