Skip to content

Commit

Permalink
Added User table and middleware around user creation.
Browse files Browse the repository at this point in the history
1. Added user table.
2. Added Middleware around upserting/rejecting users.
3. Created script to bulk migrate all questionnaires.
4. Added migration script to update questionnaires with users.
5. Added proxy to the f/e app.
  • Loading branch information
SamGodwin2 committed Jun 11, 2019
1 parent ca7eae1 commit 9a87d90
Show file tree
Hide file tree
Showing 151 changed files with 2,007 additions and 1,369 deletions.
1 change: 0 additions & 1 deletion eq-author-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ In most cases sensible defaults have been selected.
| `EQ_AUTHOR_API_VERSION` | The current Author API version. This is what gets reported on the /status endpoint | No |
| `PORT` | The port which express listens on (defaults to `4000`) | No |
| `NODE_ENV` | Sets the environment the code is running in | No |
| `DATASTORE` | Sets place we store the data, allows us to have the data stored locally in JSON files which makes debugging easier (for this set it to `filesystem`) | No |
| `ENABLE_IMPORT` | When enabled it exposes a post endpoint for importing questionnaires | No |

## Run using Docker
Expand Down
1 change: 1 addition & 0 deletions eq-author-api/constants/authorTeamUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports.AUTHOR_TEAM_NAME = "Author Team";
35 changes: 35 additions & 0 deletions eq-author-api/db/models/DynamoDB.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { pick } = require("lodash/fp");
let throughput = "ON_DEMAND";
let questionnanaireTableName = "author-questionnaires";
let questionnanaireVersionsTableName = "author-questionnaire-versions";
let userTableName = "author-users";

if (process.env.DYNAMO_ENDPOINT_OVERRIDE) {
dynamoose.local(process.env.DYNAMO_ENDPOINT_OVERRIDE);
Expand All @@ -19,6 +20,10 @@ if (process.env.DYNAMO_QUESTIONNAIRE_VERSION_TABLE_NAME) {
process.env.DYNAMO_QUESTIONNAIRE_VERSION_TABLE_NAME;
}

if (process.env.DYNAMO_USER_TABLE_NAME) {
userTableName = process.env.DYNAMO_USER_TABLE_NAME;
}

const baseQuestionnaireSchema = {
id: {
type: String,
Expand Down Expand Up @@ -106,6 +111,33 @@ const questionnaireVersionsSchema = new dynamoose.Schema(
}
);

const userSchema = new dynamoose.Schema(
{
id: {
type: String,
hashKey: true,
required: true,
},
email: {
type: String,
},
name: {
type: String,
},
externalId: {
type: String,
required: true,
},
picture: {
type: String,
},
},
{
throughput,
timestamps: true,
}
);

const QuestionnaireModel = dynamoose.model(
questionnanaireTableName,
questionnanaireSchema
Expand All @@ -116,9 +148,12 @@ const QuestionnaireVersionsModel = dynamoose.model(
questionnaireVersionsSchema
);

const UserModel = dynamoose.model(userTableName, userSchema);

module.exports = {
QuestionnaireModel,
QuestionnaireVersionsModel,
dynamoose,
justListFields,
UserModel,
};
1 change: 1 addition & 0 deletions eq-author-api/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ services:
- DYNAMO_ENDPOINT_OVERRIDE=http://dynamo:8000
- DYNAMO_QUESTIONNAIRE_TABLE_NAME=dev-author-questionnaires
- DYNAMO_QUESTIONNAIRE_VERSION_TABLE_NAME=dev-author-questionnaire-versions
- DYNAMO_USER_TABLE_NAME=dev-author-users
- NODE_ENV=development
- RUNNER_SESSION_URL=http://localhost:5000/session?token=
- PUBLISHER_URL=http://localhost:9000/publish/
Expand Down
61 changes: 61 additions & 0 deletions eq-author-api/keys.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
keys:
publisher:
value: |
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEApEJbinF5XUgXS9MTqwYyDClmEYHQRW4T20ofE+9568GVAGwy
n80T0F8sLM3bW9GNj3xxnd/4NKG/E8kD3pXNq2IdBoCpr6OOJgs/JY+5aztOIQpl
cxzy0jH6BPAVBkmNwmx1ebsDRdZMjQwvOAFTce/8vhH+xRFFChX5cE3n3MZg+5Fy
DlXePbXNWwHEY0CWLD317FNKJt1IS5JanjY0jHKX3/+wifKiZ3y03OwMLH9BEMTG
dw5rQlKq48hW2ZYFrvqYN/jqVG5BSVjUu6uZoLu+Bjn93Yk9rCyIkDYSlp924aXC
pWR/vDvfw8wW9OOqQlYmuoKjz3YQ5rNGLUfGYQIDAQABAoIBABtFjS9KMc1EB+Il
9HEDwVF1miWz+OFeKlvRTaGgtAxpgpSkYi9X/D8vXgU+VHpFK4y0K6T7p5kNdc0S
JdtR4ejfjFQlDodDz3kPivdwimd8XDduI3PeTY6Kj6k8NzFpCulJ2qNQYnW449LT
QA+7YKBdKQhhsnwDpwPKrDRyKtnjrdLJCbricWqRk1cCOQgeyY4is8NX3FETjLvF
VDX5/fhptiEvtW+41VEJUQjN7GsQ6lS8lRmWkFOlMMmX+YN9pe8GEaP4CRvjWVcq
bBzew+GP6BFcpMKUtootdc0WfhBgFABC0cPR9L2CFdrqwrCrwr6VPFjtG12KBvrw
K+WeHcUCgYEA1ovQAIEWFrv0V6CNz+07O4IjmHPNVwnReP+qWGrNthf6t/u9UWt/
PrlhI4L+SO2zbipVHwx9TnfbVfaiX1OzgAi7dsUMxrUXhgIDVQ1mVb5Wu/66jiYB
uAYzlxKx7JJ+ZLiVMokBc2/sw1oU88SBdp/J8isD6fNAiVCL1toOdM8CgYEAw/8u
Vs2Xk3DW/U1YBvIDhtOxb5v20WB6dn9DyfCiEP8lNejf2SsOFIOUPMSaMEtaFXGB
/ExvtK2J+oosvX5sQ526zDt576EFoLRLVBPvR2feoQUm4IBSxWjlTG8KQBBcKZvt
0AT12Eq8ny1mitcEOV4VdwVtKvbJIJvMCQ6ePc8CgYBpXbzHop3wiFpV9rnu1flz
HlTQ+H5uMpo3rIhCaCGjPl+Zn/64T+zsJyr3v8uWkXa/sfagCwg3U1HbBAd2far4
RFGc4OWHaUSmQbLVQIFztsjuBwoj6bKKaDFKDppnLwL4AFb1vKeDxAIpZsJHfch/
M7aAQnPnn9mgFni3CdxzrQKBgQCCRZJfT7d34ICFTGs98o41h29gZ6Hd+Ops6Jn+
F1shQPVSp75hciShrfNRkLuLbgoqRo/HSmbVCKO1SzIBoY4EQ0pthPq/M5+x+SQ4
dieMppVrISl9/s0FOXtvGj8N5dHcNjATG7jsUoCcrGiDz/aWytopignNjMqSSfuL
6ZN0MQKBgQCfJfk/SOl+niT7gdyN+Fc+TNwBoTIsrIm6VOOkDSCxKR5qY0cIAE1j
yZW1CKeZg5kcYP66JmGAFZq87ouHygCXFkZkcaboYjV6URL+4vRRiQYt4TUQGiJm
LKpPms/WjRAUCxnsslcURTcTYehZAeOAic9ITWv1ppr6AN3AL71qdQ==
-----END RSA PRIVATE KEY-----
use: signing
notPublisher:
value: |
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAvRLvLCpQzpZXqEeZMcFqzlumtDoC5D6jAbBflHBKCpl9uIeG
0UwAyherdvv2782LvI4VYELgQ7+4QUIFBONBkt08Cosx1Fg3I426AbwvnXZeaNq2
mrV1J7111pURZ77oAIWTX5rwn0AUHLXK0KFy2PkdishJ8LPEi9ow/8NOmmjXrdvx
RUVTrddqqqxEJEoyG2dG9SFMYamx6f6sAI35Aocxr6X8jqeKqg85IOx/B/1RMxM8
9SvDyAP6gdSF4aL8nU4uOTX7gHlIjpHWE2C24DtOSJ0pywUOi3iMHwSqL/hBGSP2
fCRJWo1+Zk0OA/R9bI3InKXtqt3TpO/Zu1qtQQIDAQABAoIBAQCfIuibS6JnvrJY
uKp/7kNvHETbPFhiszWPnltUHI61DSt7vNYEIuwnLHTs2HWmsGIEebIUKzWG0D7M
7jw7OOtgKUT3uuBd0UHXctozy6hn59Def/dhj4jjwdsEmMLlhxDOH59CwK3124ES
CRCDON/pLwNmY92X8jZjX2oOWtw1qMVjdy7qwbOty/QByEKQMg2HOtD8GCnlM7U+
YmHQFmGacFJG6f2LS+5kXWvy8jCfY91IIoAATgPuTDDv1HQtOrX1GYGHHvOOE9w6
Mnu37uQ3ahR0TO+W4GzCUFuKSzfJjJtqGxgGlIH09eFC1izicrFAyoA5y9c7xYun
wxnM9GHhAoGBAOvCDsms7nZ02gsbj2zuFl0QaWJObQaTgxlyUcNBh/Ju8OkB93hi
ouQf7UY4tUTGNf3BUBUUfs9pz1PoCcWMdO1L1/QXabDB45S7YIy5ZY9YzabhDNZ7
bezAB8Qz/8RxlT1VBK0cUrNZoMTkiTKjeL35nqWm98W5tHhtcfupA/9PAoGBAM1O
w60GfwtWnrAG1PCkfUhbjwbjelydQ4tZ+XeHxpDDsks+bOPKQJrhQVIjvboeomYQ
cy5ALc6zJ59OD/ChAE2L1ICZRluKT45oml6T46IF+Dxx/Yn7t6O7MRQhV/slBrpL
iUc2jE1MqtR+8ihEThATWSugtjVF3cQBZsQxYuZvAoGBAIel5HRNt/cYTMzPUrrO
UrfBVaFmlnyOK4Rcroa2Ec5/3sVyiD0QI8E2TYN15XZoxW/35mf6nuwgaVZR0C2G
H1DZrquXPQyiwps1JNxTYAgWhF1cZ9KQujLaWp2dfbA92iyr2kDwZvUiwMoI5Jfq
TvKfyZRueWme7CPOdSLjoJUpAoGAHU89SBpBUNEQ3nhbNe+FoyrPBGC9OzOITQCP
SK0tf8UwUuWajfp1tqapuJw5nbR54rA+gT/QSk3xPiDazbNuY6Outp0rGi5opR/x
yca7HIpVoet0EprI4Kr8mq8X7Fag2z+lXXFAHljvml51G6KEsm7QsBXFbV2IB09b
6G5UfCkCgYAJGdl9OUsunIaSdX+KzkALWGvXRW7RDoC+X+sE6Quc08oSwE3Pl/8V
C68+m80hKfre9w9VbW9eZyTgn4LFu/FRQobyHAz5zQWxymOGdA+cSLf23Nwh30qj
0TSL0gPFNKKe2cPwWOHfUJezJJ4qWh2jBTTyOec0nzVD5ZyVMmgsTg==
-----END RSA PRIVATE KEY-----
use: signing
13 changes: 13 additions & 0 deletions eq-author-api/keys.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,16 @@ keys:
-----END PUBLIC KEY-----
version: sr

publisher:
use: verification
value: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApEJbinF5XUgXS9MTqwYy
DClmEYHQRW4T20ofE+9568GVAGwyn80T0F8sLM3bW9GNj3xxnd/4NKG/E8kD3pXN
q2IdBoCpr6OOJgs/JY+5aztOIQplcxzy0jH6BPAVBkmNwmx1ebsDRdZMjQwvOAFT
ce/8vhH+xRFFChX5cE3n3MZg+5FyDlXePbXNWwHEY0CWLD317FNKJt1IS5JanjY0
jHKX3/+wifKiZ3y03OwMLH9BEMTGdw5rQlKq48hW2ZYFrvqYN/jqVG5BSVjUu6uZ
oLu+Bjn93Yk9rCyIkDYSlp924aXCpWR/vDvfw8wW9OOqQlYmuoKjz3YQ5rNGLUfG
YQIDAQAB
-----END PUBLIC KEY-----
29 changes: 0 additions & 29 deletions eq-author-api/middleware/auth.js

This file was deleted.

61 changes: 61 additions & 0 deletions eq-author-api/middleware/identification/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const verifyServiceRequest = require("./verifyServiceRequest");
const { isNil, isEmpty } = require("lodash/fp");
const jwt = require("jsonwebtoken");
const { getUserByExternalId } = require("../../utils/datastore");

module.exports = logger => async (req, res, next) => {
const authHeader = req.header(process.env.AUTH_HEADER_KEY || "authorization");
if (isNil(authHeader)) {
logger.error("Request must contain a valid authorization header.");
res.send(401);
return;
}

const accessToken = authHeader.replace("Bearer ", "").replace(/=/g, "");
if (isEmpty(accessToken)) {
logger.error("Request must contain a valid access token.");
res.send(401);
return;
}

const jwtToken = jwt.decode(accessToken);
if (isNil(jwtToken)) {
logger.error("Could not decode JWT token.");
res.send(401);
return;
}

if (jwtToken.serviceName) {
await verifyServiceRequest(accessToken, jwtToken.serviceName);
req.user = {
id: jwtToken.serviceName,
name: jwtToken.serviceName,
isVerified: true,
};
next();
return;
}

let user = await getUserByExternalId(jwtToken.sub);
if (!user) {
req.user = {
name: jwtToken.name,
externalId: jwtToken.sub,
email: jwtToken.email,
isVerified: false,
};
next();
return;
}

req.user = {
id: user.id,
name: jwtToken.name,
externalId: jwtToken.sub,
email: jwtToken.email,
isVerified: true,
};

next();
return;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
const createAuthMiddleware = require("./auth");
const identificationMiddleware = require("./");
const jwt = require("jsonwebtoken");
const uuid = require("uuid");
const yaml = require("js-yaml");
const fs = require("fs");

const keysFile = "./keys.test.yml";
const keysYaml = yaml.safeLoad(fs.readFileSync(keysFile, "utf8"));
const keysJson = JSON.parse(JSON.stringify(keysYaml));

const { createUser } = require("../../utils/datastore");

describe("auth middleware", () => {
let logger;
Expand All @@ -12,11 +20,11 @@ describe("auth middleware", () => {
});

it("should export a function", () => {
expect(createAuthMiddleware).toEqual(expect.any(Function));
expect(identificationMiddleware).toEqual(expect.any(Function));
});

it("should create middleware function", () => {
const middleware = createAuthMiddleware(logger);
const middleware = identificationMiddleware(logger);
expect(middleware).toEqual(expect.any(Function));
});

Expand All @@ -35,7 +43,7 @@ describe("auth middleware", () => {
};
next = jest.fn();

middleware = createAuthMiddleware(logger);
middleware = identificationMiddleware(logger);
});

it("should check the authorization header", () => {
Expand Down Expand Up @@ -85,42 +93,53 @@ describe("auth middleware", () => {
});

describe("valid token", () => {
it("should add token payload to request if valid token", () => {
let payload = {
payload: {
data: {
some: "value",
},
},
};

const expected = jwt.sign(payload, uuid.v4());
it("if user exists should add user to request", async () => {
let sub = uuid.v4();
let auth = { name: "foo", sub };
await createUser({ name: "foo", externalId: sub });
const expected = jwt.sign(auth, uuid.v4());
req.header.mockImplementation(() => `Bearer ${expected}`);
await middleware(req, res, next);
expect(req.user).toMatchObject({
id: expect.any(String),
externalId: sub,
name: "foo",
isVerified: true,
});
expect(next).toHaveBeenCalled();
});

it("if user doesn't exist should add temp user to request", async () => {
let sub = uuid.v4();
let auth = { name: "foo", sub };

const expected = jwt.sign(auth, uuid.v4());
req.header.mockImplementation(() => `Bearer ${expected}`);
middleware(req, res, next);
expect(req.auth).toMatchObject(payload);
await middleware(req, res, next);
expect(req.user).toMatchObject({
externalId: sub,
name: "foo",
isVerified: false,
});
expect(next).toHaveBeenCalled();
});

it("should add token payload to request if valid token with padding", () => {
let payload = {
payload: {
data: {
some: "value",
},
},
};

const expected = jwt.sign(payload, uuid.v4());
const position = expected.indexOf(".");
const expectedWithEquals = [
expected.slice(0, position),
"==",
expected.slice(position),
].join("");
req.header.mockImplementation(() => `Bearer ${expectedWithEquals}`);
middleware(req, res, next);
expect(req.auth).toMatchObject(payload);
it("if user is a service should add service to list", async () => {
const oldKeysFile = process.env.KEYS_FILE;
process.env.KEYS_FILE = "./keys.test.yml";
let auth = { serviceName: "publisher" };
const expected = jwt.sign(auth, keysJson.keys.publisher.value, {
algorithm: "RS256",
});
req.header.mockImplementation(() => `Bearer ${expected}`);
await middleware(req, res, next);
expect(req.user).toMatchObject({
id: "publisher",
name: "publisher",
isVerified: true,
});
expect(next).toHaveBeenCalled();
process.env.KEYS_FILE = oldKeysFile;
});
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = async (req, res, next) => {
if (!req.user.isVerified) {
res.status(401).send("User does not exist");
return;
}

next();
};
Loading

0 comments on commit 9a87d90

Please sign in to comment.