Skip to content

TC-email-service release #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Mar 19, 2018
Merged
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
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,4 @@ workflows:
- "build-prod":
filters:
branches:
only: master
only: master
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ RUN apt-get update
WORKDIR /opt/app
COPY . .
RUN npm install
ENTRYPOINT ["npm", "start"]
RUN npm run lint
CMD ["npm", "start"]
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ The other configurations can be changed in `config/default.js` or by setting env
- `JWT_TOKEN_SECRET` the secret to sign JWT tokens
- `JWT_TOKEN_EXPIRES_IN` the JWT token expiration
- `KAFKA_TOPIC_PREFIX` the prefix of all topics in Kafka
- `TC_EMAIL_URL` the email service URL (http://localhost:4001, if deployed locally)
- `TC_EMAIL_TOKEN` the email service authentication token (see tc-email README for details **link should be added later**)
- `TC_EMAIL_CACHE_PERIOD` the period to cache template placeholders from email service (60 min default)

## Code Standard
The code follows StandardJS:
Expand All @@ -104,12 +107,14 @@ To generate JWT Tokens for allowed services, run:
npm run start
```

- Import `docs/topcoder-notifications - bus-api-server.postman_collection.json` and `docs/topcoder-notifications - bus-api-server.postman_environment.json` to Postman
- Import `docs/tc-bus-api-server.postman_collection.json` and `docs/tc-bus-api-server.postman_environment.json` to Postman
- Change `URL` environment variable in Postman according to your deployment. If you deploy locally, it should be `http://localhost:3000/api/v1` by default
- Change `VALID_TOKEN` if you want to test with another JWT token
- Change `VALID_MESSAGE_TYPE` if you want to test with another message type
- Change `EMAIL` to a valid e-mail address
- Execute calls to verify the endpoints


### Verify the messsages end up in Kafka queue
Check `bus-api-test/README.md` to run a consumer that consumes and prints all messages in Kafka queue.

Expand Down
93 changes: 89 additions & 4 deletions common/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const _ = require('lodash')
const Joi = require('joi')
const getParams = require('get-parameter-names')
const config = require('config')
const jwt = require('jsonwebtoken')
const createError = require('http-errors')

const logger = require('./logger')
Expand Down Expand Up @@ -89,27 +90,111 @@ function buildService (service) {
decorateWithLogging(service)
}

/**
* Verify the JWT token and get the payload.
*
* @param {String} token the JWT token to verify
* @returns {Object} the payload decoded from the token
*/
function verifyJwtToken (token) {
let payload

try {
payload = jwt.verify(token, config.JWT_TOKEN_SECRET)
} catch (err) {
if (err.message === 'jwt expired') {
throw createError.Unauthorized('Token has been expired')
}

throw createError.Unauthorized('Failed to verify token')
}

if (!payload) {
throw createError.Unauthorized('Failed to decode token')
}

return payload
}

/**
* Sign the payload and get the JWT token.
*
* @param {Object} payload the payload to be sign
* @returns {String} the token
*/
function signJwtToken (payload) {
return jwt.sign(payload, config.JWT_TOKEN_SECRET, {expiresIn: config.JWT_TOKEN_EXPIRES_IN})
}

/**
* Validate the event based on the source service, type, and message.
*
* @param {String} sourceServiceName the source service name
* @param {Object} event the event
*/
function validateEvent (sourceServiceName, event) {
const schema = Joi.object().keys({
sourceServiceName: Joi.string().required(),
event: Joi.object().keys({
type: Joi
.string()
.regex(/^([a-zA-Z0-9]+\.)+[a-zA-Z0-9]+$/)
.error(createError.BadRequest(
'"type" must be a fully qualified name - dot separated string'))
.required(),
message: Joi.string().required()
})
})

const { error } = Joi.validate({sourceServiceName, event}, schema)
if (error) {
throw error
}

// The message should be a JSON-formatted string
let message
try {
JSON.parse(event.message)
message = JSON.parse(event.message)
} catch (err) {
logger.error(err)
throw createError.BadRequest(
`"message" is not a valid JSON-formatted string: ${err.message}`)
}

// The message should match with the source service and type
// no-op for now
return message
}

/**
* Validate the event payload
*
* @param {Object} event the event payload
*/
function validateEventPayload (event) {
const schema = Joi.object().keys({
event: Joi.object().keys({
topic: Joi
.string()
.regex(/^([a-zA-Z0-9]+\.)+[a-zA-Z0-9]+$/)
.error(createError.BadRequest(
'"topic" must be a fully qualified name - dot separated string'))
.required(),
originator: Joi.string().required(),
timestamp: Joi.string().required(),
'mime-type': Joi.string().required(),
payload: Joi.any()
})
})

const { error } = Joi.validate({event}, schema)
if (error) {
throw error
}
}

module.exports = {
buildService,
validateEvent
verifyJwtToken,
signJwtToken,
validateEvent,
validateEventPayload
}
10 changes: 7 additions & 3 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ module.exports = {
PORT: process.env.PORT || '3000',
authSecret: process.env.JWT_TOKEN_SECRET,
authDomain: process.env.AUTH_DOMAIN,
jwksUri: process.env.jwksUri,
validIssuers: process.env.VALID_ISSUERS ? process.env.VALID_ISSUERS.replace(/\\"/g, '') : null,
KAFKA_TOPIC_PREFIX: process.env.KAFKA_TOPIC_PREFIX || 'joan-26673.notifications.',
ALLOWED_SERVICES: ['project-service', 'message-service']
JWT_TOKEN_SECRET: process.env.JWT_TOKEN_SECRET || '',
JWT_TOKEN_EXPIRES_IN: process.env.JWT_TOKEN_EXPIRES_IN || '100 days',
KAFKA_TOPIC_PREFIX: process.env.KAFKA_TOPIC_PREFIX || '',
ALLOWED_SERVICES: process.env.ALLOWED_SERVICES || ['project-service', 'message-service'],
TC_EMAIL_SERVICE_URL: process.env.TC_EMAIL_SERVICE_URL,
TC_EMAIL_SERVICE_TOKEN: process.env.TC_EMAIL_SERVICE_TOKEN,
TC_EMAIL_SERVICE_CACHE_PERIOD: process.env.TC_EMAIL_SERVICE_CACHE_PERIOD || (3600 * 1000)
}
21 changes: 21 additions & 0 deletions controllers/PlaceholderController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* The Placeholder controller.
*/
const PlaceholderService = require('../services/PlaceholderService')

/**
* Clear placeholder cache.
*
* @param {Object} req the request
* @param {Object} res the response
* @param {Function} next the next middleware
*/
async function clearAll (req, res, next) {
await PlaceholderService.clearAllPlaceholders()
res.status(200).end()
next()
}

module.exports = {
clearAll
}
18 changes: 14 additions & 4 deletions deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ KAFKA_CLIENT_CERT_KEY=$(eval "echo \$${ENV}_KAFKA_CLIENT_CERT_KEY")
AUTH_DOMAIN=$(eval "echo \$${ENV}_AUTH_DOMAIN")
VALID_ISSUERS=$(eval "echo \$${ENV}_VALID_ISSUERS")

TC_EMAIL_SERVICE_URL=$(eval "echo \$${ENV}_TC_EMAIL_SERVICE_URL")
TC_EMAIL_SERVICE_TOKEN=$(eval "echo \$${ENV}_TC_EMAIL_SERVICE_TOKEN")

echo $APP_NAME

configure_aws_cli() {
Expand Down Expand Up @@ -143,8 +146,15 @@ make_task_def(){
{
"name": "VALID_ISSUERS",
"value": "%s"
}

},
{
"name": "TC_EMAIL_SERVICE_URL",
"value": "%s"
},
{
"name": "TC_EMAIL_SERVICE_TOKEN",
"value": "%s"
}
],
"portMappings": [
{
Expand All @@ -164,7 +174,7 @@ make_task_def(){
}
]'

task_def=$(printf "$task_template" $AWS_ECS_CONTAINER_NAME $AWS_ACCOUNT_ID $AWS_REGION $AWS_REPOSITORY $TAG $ENV $KAFKA_URL "$KAFKA_CLIENT_CERT" "$KAFKA_CLIENT_CERT_KEY" $LOG_LEVEL $JWT_TOKEN_SECRET "$KAFKA_TOPIC_PREFIX" "$ALLOWED_SERVICES" $JWT_TOKEN_EXPIRES_IN "$API_VERSION" $PORT "$AUTH_DOMAIN" "$VALID_ISSUERS" $AWS_ECS_CLUSTER $AWS_REGION $AWS_ECS_CLUSTER $ENV)
task_def=$(printf "$task_template" $AWS_ECS_CONTAINER_NAME $AWS_ACCOUNT_ID $AWS_REGION $AWS_REPOSITORY $TAG $ENV $KAFKA_URL "$KAFKA_CLIENT_CERT" "$KAFKA_CLIENT_CERT_KEY" $LOG_LEVEL $JWT_TOKEN_SECRET "$KAFKA_TOPIC_PREFIX" "$ALLOWED_SERVICES" $JWT_TOKEN_EXPIRES_IN "$API_VERSION" $PORT "$AUTH_DOMAIN" "$VALID_ISSUERS" $TC_EMAIL_SERVICE_URL $TC_EMAIL_SERVICE_TOKEN $AWS_ECS_CLUSTER $AWS_REGION $AWS_ECS_CLUSTER $ENV)
}

register_definition() {
Expand Down Expand Up @@ -199,4 +209,4 @@ check_service_status() {
configure_aws_cli
push_ecr_image
deploy_cluster
check_service_status
check_service_status
2 changes: 1 addition & 1 deletion docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ paths:
type: "string"
description: "the event type, should be a dot separated fully qualitied\
\ name"
example: "connect.project.update"
example: "notifications.connect.project.update"
message:
type: "string"
description: "the message, should be a JSON formatted string"
Expand Down
Loading