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

Add ability to generate and assign API keys to accounts #18

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
108 changes: 95 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,12 @@ $ yarn test:cov
- [`GET` User subscriptions](#get-subscriptions)
- [`PUT` Update user](#update-user)
- [`DELETE` Remove user](#remove-user)


#### Key
- [`GET` Get key](#get-key)
- [`POST` Create key](#create-key)
- [`POST` Check key](#check-key)
- [`DELETE` Remove key](#remove-key)


## Auth
Expand Down Expand Up @@ -276,10 +281,11 @@ HTTP/1.1 200 OK
"email":"[email protected]",
"password":null,
"emailConfirmed":false,
"firstName":Greg,
"lastName":Smith,
"companyName":Alterac,
"companyDescription":null,"id":"191e7252-662c-4b69-885a-0aa505bea6cf"
"firstName":"Greg",
"lastName":"Smith",
"companyName":"Alterac",
"companyDescription":null,
"id":"191e7252-662c-4b69-885a-0aa505bea6cf"
}
```
### Get user
Expand All @@ -300,10 +306,11 @@ HTTP/1.1 200 OK
"email":"[email protected]",
"password":null,
"emailConfirmed":false,
"firstName":Greg,
"lastName":Smith,
"companyName":Alterac,
"companyDescription":null,"id":"191e7252-662c-4b69-885a-0aa505bea6cf"
"firstName":"Greg",
"lastName":"Smith",
"companyName":"Alterac",
"companyDescription":null,
"id":"191e7252-662c-4b69-885a-0aa505bea6cf"
}
```
### Get subscriptions
Expand Down Expand Up @@ -364,10 +371,11 @@ HTTP/1.1 200 OK
"email":"[email protected]",
"password":null,
"emailConfirmed":true,
"firstName":Greg,
"lastName":Schmidt,
"companyName":Alterac,
"companyDescription":null,"id":"191e7252-662c-4b69-885a-0aa505bea6cf"
"firstName":"Greg",
"lastName":"Schmidt",
"companyName":"Alterac",
"companyDescription":null,
"id":"191e7252-662c-4b69-885a-0aa505bea6cf"
}
```
### Remove user
Expand All @@ -388,3 +396,77 @@ HTTP/1.1 200 OK
"success": true
}
```
## Key

### Get key

Retrieve current key assigned to requesting account.

GET /key

#### Headers

Requires [Authorization header](#authorization-header)

#### Success Response

```
HTTP/1.1 200 OK
{
"key": "0oi5n7nd8pj85xsmzbh4z7bz7qqgqqh1"
}
```
### Create key

Generate a new key and revoke currently assigned key.

POST /key/create

#### Headers

Requires [Authorization header](#authorization-header)

#### Success Response

```
HTTP/1.1 200 OK
{
"key" : "0oi5n7nd8pj85xsmzbh4z7bz7qqgqqh1"
}
```
### Check key

A test route to show validity of an API key.

POST /key/check

#### Headers

Requires [Authorization header](#authorization-header)

#### Success Response

```
HTTP/1.1 200 OK
{
"valid": true
}
```
### Remove key

Revoke key assigned to requesting account.

DELETE /key

#### Headers

Requires [Authorization header](#authorization-header)

#### Success Response

```
HTTP/1.1 200 OK
{
"ok": true
}
```
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,26 @@
"@types/bcrypt": "^3.0.0",
"@types/bcrypt-nodejs": "^0.0.30",
"@types/email-templates": "^3.5.0",
"@types/faker": "^4.1.4",
"@types/graphql": "^14.0.2",
"@types/nodemailer": "^4.6.5",
"@types/stripe": "^6.0.1",
"apollo-server-express": "^2.0.3",
"aws-sdk": "^2.333.0",
"bcrypt": "^3.0.1",
"class-validator": "^0.9.1",
"cors": "^2.8.4",
"dotenv": "^6.1.0",
"email-templates": "^5.0.1",
"express": "^4.16.4",
"express-sslify": "^1.2.0",
"faker": "^4.1.0",
"graphql": "^14.0.2",
"helmet": "^3.14.0",
"joi": "^13.7.0",
"joi": "^14.3.1",
"jsonwebtoken": "^8.3.0",
"nodemailer": "^4.6.8",
"juice": "^5.1.0",
"nodemailer": "^5.1.1",
"passport": "^0.4.0",
"passport-jwt": "^4.0.0",
"path": "^0.12.7",
Expand All @@ -69,7 +73,7 @@
"supertest": "^3.3.0",
"ts-jest": "^23.10.4",
"ts-loader": "^5.2.1",
"tslint": "5.11.0",
"tslint": "5.12.1",
"tslint-config-airbnb": "^5.11.0",
"webpack": "^4.20.2",
"webpack-cli": "^3.1.2",
Expand Down
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { SubscriptionModule } from './subscription/subscription.module';
import { ContactModule } from './contact/contact.module';
import { GraphQLModule } from '@nestjs/graphql';
import { ConfigService } from './config/config.service';
import { KeyModule } from './key/key.module';

// the config service cannot be passed through on first initialization since it is an import
const configService = new ConfigService('.env');
Expand All @@ -26,6 +27,7 @@ const configService = new ConfigService('.env');
PaymentModule,
ProductModule,
SubscriptionModule,
KeyModule,
],
})

Expand Down
8 changes: 4 additions & 4 deletions src/contact/contact.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ export class ContactService {
company: contact.company,
message: contact.message,
})
.then(email => {
.then((email) => {
Logger.log(email, ContactService.name);
})
.catch(error => {
.catch((error) => {
Logger.error(error, ContactService.name);
});
}
Expand Down Expand Up @@ -105,10 +105,10 @@ export class ContactService {
company: contact.company,
message: contact.message,
})
.then(email => {
.then((email) => {
Logger.log(email, ContactService.name);
})
.catch(error => {
.catch((error) => {
Logger.error(error, ContactService.name);
});
}
Expand Down
56 changes: 56 additions & 0 deletions src/key/key.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Controller, UseGuards, Post, HttpStatus, Get, Put, Param, Res, Body, Req, Logger, Delete } from '@nestjs/common';
import { KeyService } from './key.service';
import { AuthGuard } from '@nestjs/passport';

@Controller('key')
export class KeyController {
constructor(private readonly keyService: KeyService) {}

@Get('')
@UseGuards(AuthGuard('jwt'))
async getKey(@Body() body: any, @Req() req: any, @Res() res: any) {
const key = await this.keyService.getKey(req.user.id);
Logger.log(`Get key for ${req.user.id}: ${key}`, KeyController.name);
return res.status(HttpStatus.OK).send(JSON.stringify({ key }));
}

@Post('create')
@UseGuards(AuthGuard('jwt'))
async createKey(@Body() body: any, @Req() req: any, @Res() res: any) {
const userId = req.user.id;
if (await this.keyService.getKey(userId))
await this.keyService.removeKey(userId);
const key = await this.keyService.createKey(userId);
Logger.log(`Created new key for ${userId}: ${JSON.stringify(key)}`, KeyController.name);
return res.status(HttpStatus.OK).send(JSON.stringify({ key }));
}

@Post('check')
@UseGuards(AuthGuard('jwt'))
async checkKey(@Req() req: any, @Body() body: any, @Res() res: any) {
Logger.log(`Check key for ${req.user.id}`, KeyController.name);
if (req.user.id == null) {
Logger.error(`Error checking key.`, undefined, KeyController.name);
return res.status(HttpStatus.OK).send(JSON.stringify({ error: 'User not found.' }));
}
const key = await this.keyService.getKey(req.user.id);
if (key && key.key === body.key) {
return res.status(HttpStatus.OK).send(JSON.stringify({ valid: true }));
}
return res.status(HttpStatus.OK).send(JSON.stringify({ valid: false }));
}

@Delete('')
@UseGuards(AuthGuard('jwt'))
async revokeKey(@Req() req: any, @Body() body: any, @Res() res: any) {
if (req.user.id == null) {
Logger.error(`Error removing key.`, undefined, KeyController.name);
return res.status(HttpStatus.OK).send(JSON.stringify({ error: 'User not found.' }));
}
const removed = await this.keyService.removeKey(req.user.id);
if (removed) {
return res.status(HttpStatus.OK).send(JSON.stringify({ ok: true }));
}
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(JSON.stringify({ ok: false }));
}
}
22 changes: 22 additions & 0 deletions src/key/key.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Key {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
userId: string;

@Column()
key: string;

@Column({ nullable: true })
startDate: Date;

@Column({ nullable: true })
endDate: Date;

@Column({ nullable: true })
status: string;
}
16 changes: 16 additions & 0 deletions src/key/key.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { KeyController } from './key.controller';
import { KeyService } from './key.service';
import { Key } from './key.entity';
import { SubscriptionModule } from '../subscription/subscription.module';
import { ProductModule } from '../product/product.module';

@Module({
imports: [TypeOrmModule.forFeature([Key])],
providers: [KeyService],
exports: [KeyService],
controllers: [KeyController],
})

export class KeyModule {}
32 changes: 32 additions & 0 deletions src/key/key.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Key } from './key.entity';
import { random } from 'faker';

@Injectable()
export class KeyService {
constructor(
@InjectRepository(Key)
private readonly keyRepository: Repository<Key>) {}

async getKey(userId: string): Promise<Key> {
return await this.keyRepository.findOne({ userId, status: 'active' });
}

async createKey(userId: any): Promise<Key> {
Logger.log(`Creating key for ${JSON.stringify(userId)}`, KeyService.name);
const key = new Key();
key.userId = userId;
key.key = random.alphaNumeric(32);
key.status = 'active';

return await this.keyRepository.save(key);
}

async removeKey(userId: string): Promise<any> {
Logger.log(`Removing key for ${JSON.stringify(userId)}.`, KeyService.name);
const newKey = await this.getKey(userId);
return await this.keyRepository.save({ ...newKey, status: 'canceled' });
}
}
Loading