Skip to content

Commit

Permalink
Continue work on API key
Browse files Browse the repository at this point in the history
  • Loading branch information
dangtony98 committed Dec 26, 2022
1 parent d869968 commit 888d28d
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 39 deletions.
5 changes: 4 additions & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ WORKDIR /app

COPY package.json package-lock.json ./

RUN npm ci --only-production --ignore-scripts
# RUN npm ci --only-production --ignore-scripts
# "prepare": "cd .. && npm install"

RUN npm ci --only-production

COPY . .

Expand Down
1 change: 0 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"prepare": "cd .. && npm install",
"start": "npm run build && node build/index.js",
"dev": "nodemon",
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build",
Expand Down
40 changes: 40 additions & 0 deletions backend/src/middleware/requireAPIKeyDataAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Request, Response, NextFunction } from 'express';
import { APIKeyData } from '../models';
import { validateMembership } from '../helpers/membership';
import { AccountNotFoundError } from '../utils/errors';

type req = 'params' | 'body' | 'query';

const requireAPIKeyDataAuth = ({
acceptedRoles,
acceptedStatuses,
location = 'params'
}: {
acceptedRoles: string[];
acceptedStatuses: string[];
location?: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {

// req.user

const apiKeyData = await APIKeyData.findById(req[location].apiKeyDataId);

if (!apiKeyData) {
return next(AccountNotFoundError({message: 'Failed to locate API Key data'}));
}

await validateMembership({
userId: req.user._id.toString(),
workspaceId: apiKeyData?.workspace.toString(),
acceptedRoles,
acceptedStatuses
});

req.apiKeyData = '' // ??

next();
}
}

export default requireAPIKeyDataAuth;
33 changes: 18 additions & 15 deletions backend/src/models/apiKey.ts → backend/src/models/apiKeyData.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Schema, model, Types } from 'mongoose';
import { ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD } from '../variables';

// TODO: add scopes

export interface IAPIKey {
export interface IAPIKeyData {
name: string;
workspace: string;
environment: string;
workspaces: {
workspace: Types.ObjectId,
environments: string[]
}[];
expiresAt: Date;
prefix: string;
apiKeyHash: string;
Expand All @@ -15,19 +15,22 @@ export interface IAPIKey {
tag: string;
}

const apiKeySchema = new Schema<IAPIKey>(
const apiKeyDataSchema = new Schema<IAPIKeyData>(
{
name: {
type: String,
required: true
},
workspace: {
type: String
},
environment: {
type: String,
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD]
},
workspaces: [{
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace'
},
environments: [{
type: String,
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD]
}]
}],
expiresAt: {
type: Date
},
Expand Down Expand Up @@ -58,6 +61,6 @@ const apiKeySchema = new Schema<IAPIKey>(
}
);

const APIKey = model<IAPIKey>('APIKey', apiKeySchema);
const APIKeyData = model<IAPIKeyData>('APIKeyData', apiKeyDataSchema);

export default APIKey;
export default APIKeyData;
6 changes: 3 additions & 3 deletions backend/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Token, { IToken } from './token';
import User, { IUser } from './user';
import UserAction, { IUserAction } from './userAction';
import Workspace, { IWorkspace } from './workspace';
import APIKey, { IAPIKey } from './apiKey';
import APIKeyData, { IAPIKeyData } from './apiKeyData';

export {
BackupPrivateKey,
Expand Down Expand Up @@ -49,6 +49,6 @@ export {
IUserAction,
Workspace,
IWorkspace,
APIKey,
IAPIKey,
APIKeyData,
IAPIKeyData,
};
85 changes: 66 additions & 19 deletions backend/src/routes/apiKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@ import {
requireAuth
} from '../middleware';
import {
APIKey
APIKeyData
} from '../models';
import { body } from 'express-validator';
import { param, body, query } from 'express-validator';
import crypto from 'crypto';
import bcrypt from 'bcrypt';
// import * as bcrypt from 'bcrypt';
// const bcrypt = require('bcrypt');
import * as Sentry from '@sentry/node';

// POST /api/v1/api-key
// TODO: middleware
router.post(
'/',
requireAuth,
Expand All @@ -25,7 +23,7 @@ router.post(
body('tag'),
body('expiresAt'),
async (req, res) => {
let savedAPIKey;
let apiKey, apiKeyData;
try {
const {
name,
Expand All @@ -37,14 +35,13 @@ router.post(
expiresAt
} = req.body;

// api-key: 38 characters
// 6-char: prefix
// 32-char: remaining
const apiKey = crypto.randomBytes(19).toString('hex');
const saltRounds = 10; // config?
// create 38-char API key with first 6-char being the prefix
apiKey = crypto.randomBytes(19).toString('hex');

const saltRounds = 10; // TODO: add as config envar
const apiKeyHash = await bcrypt.hash(apiKey, saltRounds);

savedAPIKey = await new APIKey({
apiKeyData = await new APIKeyData({
name,
workspace,
environment,
Expand All @@ -55,22 +52,72 @@ router.post(
iv,
tag
}).save();

// 1. generate api key
// 2. hash api key with bcrypt
// 3. store hash and api key info in db
// 4. return api key

} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'xxx'
message: 'Failed to create workspace API Key'
});
}

return res.status(200).send({
apiKey: savedAPIKey
apiKey,
apiKeyData
});
}
);

// TODO: middleware
router.get(
'/',
requireAuth,
query('workspaceId').exists().trim(),
async (req, res) => {
let apiKeyData;
try {
const { workspaceId } = req.query;

apiKeyData = await APIKeyData.find({
workspace: workspaceId
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get workspace API Key data'
});
}

return res.status(200).send({
apiKeyData
});
}
);

// TODO: middleware
router.delete(
':apiKeyDataId',
requireAuth,
// TODO: requireAPIKeyDataAuth,
param('apiKeyDataId').exists().trim(),
async (req, res) => {
let apiKeyData;
try {
const { apiKeyDataId } = req.params;

apiKeyData = await APIKeyData.findByIdAndDelete(apiKeyDataId);

} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete API key data'
});
}

return res.status(200).send({
apiKeyData
});
}
);
Expand Down

0 comments on commit 888d28d

Please sign in to comment.