Compare commits
10 Commits
72d7ffd5c0
...
3b723cbad6
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3b723cbad6 | ||
![]() |
b050f709c9 | ||
![]() |
bea6ab8e09 | ||
![]() |
1ce0aaf927 | ||
![]() |
c8e4b690c5 | ||
![]() |
ce2c65544c | ||
![]() |
7ba8716917 | ||
![]() |
473fa4b662 | ||
![]() |
97a746c572 | ||
![]() |
0a89138a79 |
93
Jenkinsfile
vendored
93
Jenkinsfile
vendored
@ -1,47 +1,52 @@
|
|||||||
def label = "node-${UUID.randomUUID().toString()}"
|
pipeline {
|
||||||
|
agent {
|
||||||
podTemplate(label: label, inheritFrom: 'base', , containers: [
|
kubernetes {
|
||||||
containerTemplate(name: 'base', image: 'taylorchristieyardstick/build-base:node')
|
label "kaniko-${UUID.randomUUID()}"
|
||||||
]) {
|
inheritFrom 'kaniko'
|
||||||
node(label) {
|
containerTemplate {
|
||||||
stage('Checkout Project') {
|
name 'braid'
|
||||||
container('base') {
|
image 'node:8'
|
||||||
checkout scm
|
command 'sleep 9999'
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Install Dependencies') {
|
|
||||||
container('base') {
|
|
||||||
sh "npm install"
|
|
||||||
sh "npm install typescript"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Build the Project') {
|
|
||||||
container('base') {
|
|
||||||
sh "npx tsc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run unit tests') {
|
|
||||||
container('base') {
|
|
||||||
sh "npm test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Login to Dockerhub') {
|
|
||||||
withCredentials([usernamePassword(credentialsId: 'DockerHubAccessYardstick', usernameVariable: 'USER', passwordVariable: 'PASS')]) {
|
|
||||||
container('base') {
|
|
||||||
sh "docker login --username ${USER} --password ${PASS}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Build the Docker Image') {
|
|
||||||
container('base') {
|
|
||||||
sh "make build branch=${BRANCH_NAME}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Push the Docker Image') {
|
|
||||||
container('base') {
|
|
||||||
sh "make push branch=${BRANCH_NAME}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
environment {
|
||||||
|
REPOSITORY = "yardstick/braid"
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Exec NPM commands') {
|
||||||
|
steps {
|
||||||
|
container('braid') {
|
||||||
|
checkout scm
|
||||||
|
sh 'npm install'
|
||||||
|
sh 'npm install typescript'
|
||||||
|
sh 'npx tsc'
|
||||||
|
sh 'npm test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Build with Kaniko') {
|
||||||
|
steps {
|
||||||
|
withCredentials([usernamePassword(credentialsId: 'DockerHubAccessYardstick', usernameVariable: 'USER', passwordVariable: 'PASS')]) {
|
||||||
|
container('kaniko') {
|
||||||
|
checkout scm
|
||||||
|
// Setup docker credentials
|
||||||
|
sh 'echo "{\\"auths\\":{\\"https://index.docker.io/v1/\\":{\\"auth\\":\\"$(printf "%s:%s" "$USER" "$PASS" | base64 | tr -d \'\n\')\\"}}}" > /kaniko/.docker/config.json'
|
||||||
|
// Execute kaniko build
|
||||||
|
sh """
|
||||||
|
/kaniko/executor -f `pwd`/Dockerfile \
|
||||||
|
-c `pwd` \
|
||||||
|
--insecure=true \
|
||||||
|
--insecure-registry=docker-registry.default:5000 \
|
||||||
|
--cache=true \
|
||||||
|
--cache-repo=docker-registry.default:5000/${env.REPOSITORY} \
|
||||||
|
--destination ${env.REPOSITORY}:\$(echo ${BRANCH_NAME} | grep -Eo 'feature/([A-Za-z]+-[0-9]*)' | grep -Eo '[A-Za-z]+-[0-9]*' || \
|
||||||
|
echo ${BRANCH_NAME} | grep -Eo '(release|hotfix)/[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+' | grep -Eo '[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+' || \
|
||||||
|
echo ${BRANCH_NAME} | grep -Eo 'YASDEV-([[:digit:]]*)')
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
# BRAID v1.3.0
|
# BRAID v1.3.1
|
||||||
> Websocket server for the Measure platform
|
> Websocket server for the Measure platform
|
||||||
|
|
||||||
[](https://semaphoreci.com/yardstick/braid)
|
[](https://semaphoreci.com/yardstick/braid)
|
||||||
|
@ -1 +1 @@
|
|||||||
1.3.0
|
1.3.1
|
||||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "braid",
|
"name": "braid",
|
||||||
"version": "1.2.3",
|
"version": "1.2.2",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "braid",
|
"name": "braid",
|
||||||
"version": "1.2.2",
|
"version": "1.3.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import PublicChannel from './channels/types/publicChannel';
|
import PublicChannel from './channels/types/publicChannel';
|
||||||
import PrivateChannel from './channels/types/privateChannel';
|
import PrivateChannel from './channels/types/privateChannel';
|
||||||
import CustomChannel from './channels/types/customChannel';
|
|
||||||
import PublicClient from './clients/types/publicClient';
|
import PublicClient from './clients/types/publicClient';
|
||||||
import PrivateClient from './clients/types/privateClient';
|
import PrivateClient from './clients/types/privateClient';
|
||||||
import CustomClient from './clients/types/customClient';
|
import CustomClient from './clients/types/customClient';
|
||||||
@ -17,8 +16,8 @@ class ChannelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createChannel(data: any) {
|
createChannel(data: any) {
|
||||||
const channelExists: PublicChannel|PrivateChannel|CustomChannel|null = this.channelExists(data.channel);
|
const channelExists: PublicChannel|PrivateChannel|null = this.channelExists(data.channel);
|
||||||
let channel: PublicChannel|PrivateChannel|CustomChannel;
|
let channel: PublicChannel|PrivateChannel;
|
||||||
|
|
||||||
if (channelExists) {
|
if (channelExists) {
|
||||||
channel = channelExists;
|
channel = channelExists;
|
||||||
@ -43,7 +42,7 @@ class ChannelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addClientToChannel(client: PublicClient|PrivateClient|CustomClient, channel_id: string) {
|
addClientToChannel(client: PublicClient|PrivateClient|CustomClient, channel_id: string) {
|
||||||
const channel: PrivateChannel|PrivateChannel|CustomChannel|null = this.channelExists(channel_id);
|
const channel: PrivateChannel|PrivateChannel|null = this.channelExists(channel_id);
|
||||||
|
|
||||||
if (channel) {
|
if (channel) {
|
||||||
channel.addClient(client);
|
channel.addClient(client);
|
||||||
@ -54,7 +53,7 @@ class ChannelManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateChannelContent(channel: PrivateChannel|PrivateChannel|CustomChannel, channelContent: JSON) {
|
updateChannelContent(channel: PrivateChannel|PrivateChannel, channelContent: JSON) {
|
||||||
if (channel) {
|
if (channel) {
|
||||||
channel.channelContent = channelContent;
|
channel.channelContent = channelContent;
|
||||||
return {status: 'success'};
|
return {status: 'success'};
|
||||||
@ -70,12 +69,10 @@ class ChannelManager {
|
|||||||
`attempting to create channel of type ${data.channel_type}, channel id: ${data.channel}...`
|
`attempting to create channel of type ${data.channel_type}, channel id: ${data.channel}...`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (data.channel_type === 'public') {
|
if (data.channel_type === 'private') {
|
||||||
return new PublicChannel(data.channel);
|
|
||||||
} else if (data.channel_type === 'private') {
|
|
||||||
return new PrivateChannel(data.channel);
|
return new PrivateChannel(data.channel);
|
||||||
} else {
|
} else {
|
||||||
return new CustomChannel(data.channel, data.custom);
|
return new PublicChannel(data.channel);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import CustomClient from '../../clients/types/customClient';
|
|
||||||
import ChannelBase from '../channelBase';
|
|
||||||
|
|
||||||
class CustomChannel extends ChannelBase {
|
|
||||||
clients: CustomClient[] = [];
|
|
||||||
|
|
||||||
constructor(id: string, custom: any) {
|
|
||||||
super(id);
|
|
||||||
|
|
||||||
if (custom) {
|
|
||||||
this.broadcastConditions = custom.broadcastConditions;
|
|
||||||
this.channelContent = custom.channelContent;
|
|
||||||
this.explicitRemoval = custom.explicitRemoval;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CustomChannel;
|
|
@ -3,7 +3,6 @@ import ClientManager from '../clientManager';
|
|||||||
import ChannelManager from '../channelManager';
|
import ChannelManager from '../channelManager';
|
||||||
import PublicChannel from '../channels/types/publicChannel';
|
import PublicChannel from '../channels/types/publicChannel';
|
||||||
import PrivateChannel from '../channels/types/privateChannel';
|
import PrivateChannel from '../channels/types/privateChannel';
|
||||||
import CustomChannel from '../channels/types/customChannel';
|
|
||||||
|
|
||||||
const messageManager = require('../messageManager');
|
const messageManager = require('../messageManager');
|
||||||
const logger = require('../logger');
|
const logger = require('../logger');
|
||||||
@ -12,7 +11,7 @@ class ClientBase {
|
|||||||
ws: WebSocket;
|
ws: WebSocket;
|
||||||
data: any;
|
data: any;
|
||||||
id: number;
|
id: number;
|
||||||
channel: PublicChannel|PrivateChannel|CustomChannel|null;
|
channel: PublicChannel|PrivateChannel|null;
|
||||||
clientManager: ClientManager;
|
clientManager: ClientManager;
|
||||||
channelManager: ChannelManager;
|
channelManager: ChannelManager;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
@ -68,7 +67,7 @@ class ClientBase {
|
|||||||
return this.data.client;
|
return this.data.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectToChannel(channel: PublicChannel|PrivateChannel|CustomChannel) {
|
connectToChannel(channel: PublicChannel|PrivateChannel) {
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
this.ws.on('message', this.messageListener);
|
this.ws.on('message', this.messageListener);
|
||||||
this.ws.on('close', this.closeListener);
|
this.ws.on('close', this.closeListener);
|
||||||
|
@ -16,5 +16,5 @@ module.exports = {
|
|||||||
algorithm: ['HS256']
|
algorithm: ['HS256']
|
||||||
},
|
},
|
||||||
messageTypes : ['broadcast', 'direct', 'changeChannel'],
|
messageTypes : ['broadcast', 'direct', 'changeChannel'],
|
||||||
archimedesToken: process.env.ARCHIMEDES_INTEGRATION_TOKEN || 'sha1=baddbeef'
|
archimedesToken: process.env.ARCHIMEDES_INTEGRATION_TOKEN || 'testkeyformeasure'
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,7 @@ const AUTH_TOKEN = app.archimedesToken;
|
|||||||
const HTTP_PORT = app.port;
|
const HTTP_PORT = app.port;
|
||||||
|
|
||||||
import { Server, IncomingMessage, ServerResponse } from 'http'
|
import { Server, IncomingMessage, ServerResponse } from 'http'
|
||||||
|
import * as bodyParser from "body-parser";
|
||||||
interface ArchimedesMessage {
|
interface ArchimedesMessage {
|
||||||
examId: string,
|
examId: string,
|
||||||
reservation_no: string
|
reservation_no: string
|
||||||
@ -25,8 +25,12 @@ class XhrListener {
|
|||||||
this.#cm = cm;
|
this.#cm = cm;
|
||||||
logger.debugLog.info("XhrListener running");
|
logger.debugLog.info("XhrListener running");
|
||||||
|
|
||||||
app.use(jsonParser);
|
//app.use(jsonParser);
|
||||||
|
|
||||||
|
// turns out that we must compute HMAC based on the payload of the POST body, so:
|
||||||
|
app.use(bodyParser.json({
|
||||||
|
verify: ((req: any, res: any, buf: any) => { req.rawBody = buf })
|
||||||
|
}));
|
||||||
// TODO: try to pin down origin to reflect client rather than use wildcard
|
// TODO: try to pin down origin to reflect client rather than use wildcard
|
||||||
app.options('*', cors());
|
app.options('*', cors());
|
||||||
|
|
||||||
@ -39,19 +43,22 @@ class XhrListener {
|
|||||||
app.post('/event/pause', cors(),
|
app.post('/event/pause', cors(),
|
||||||
(req: any, res: any, next: any) => this.eventFromArchimedes.bind(this)("pauseExam", req, res));
|
(req: any, res: any, next: any) => this.eventFromArchimedes.bind(this)("pauseExam", req, res));
|
||||||
|
|
||||||
|
|
||||||
server.on('request', app);
|
server.on('request', app);
|
||||||
|
|
||||||
// note that this kicks off HTTP listen for the entire app - not just HTTP but WS as well!
|
// note that this kicks off HTTP listen for the entire app - not just HTTP but WS as well!
|
||||||
server.listen(HTTP_PORT, () => logger.debugLog.info(`braid is now listening for HTTP and WS connections on port ${HTTP_PORT}`));
|
server.listen(HTTP_PORT, () => logger.debugLog.info(`braid is now listening for HTTP and WS connections on port ${HTTP_PORT}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
public authorized(authOffered: String): boolean {
|
public authorized(authOffered: String, rawBody: Buffer): boolean {
|
||||||
return AUTH_TOKEN === authOffered;
|
var hash = crypto.createHmac('sha1', AUTH_TOKEN).update(rawBody).digest("hex");
|
||||||
|
|
||||||
|
return authOffered.split("=").reverse()[0] === hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public eventFromArchimedes(eventType: string, req: any, res: any) {
|
public eventFromArchimedes(eventType: string, req: any, res: any) {
|
||||||
logger.debugLog.info(`eventFromArchimedes("${eventType}") called`);
|
logger.debugLog.info(`eventFromArchimedes("${eventType}") called`);
|
||||||
let authorized: boolean = this.authorized(req.headers['x-proctoru-signature']);
|
let authorized: boolean = this.authorized(req.headers['x-proctoru-signature'], req.rawBody);
|
||||||
|
|
||||||
if (!authorized) {
|
if (!authorized) {
|
||||||
res.status(401);
|
res.status(401);
|
||||||
@ -74,15 +81,15 @@ class XhrListener {
|
|||||||
public relayEvent(event: string, archMsg: ArchimedesMessage): boolean {
|
public relayEvent(event: string, archMsg: ArchimedesMessage): boolean {
|
||||||
logger.debugLog.info(`XhrListener:relayEvent(event: ${event}, channel: ${archMsg.reservation_no})`);
|
logger.debugLog.info(`XhrListener:relayEvent(event: ${event}, channel: ${archMsg.reservation_no})`);
|
||||||
for (let c of this.#cm.channels) {
|
for (let c of this.#cm.channels) {
|
||||||
if (archMsg.reservation_no === c.id) {
|
if (archMsg.reservation_no == c.id) {
|
||||||
let dmCount: number = 0;
|
let dmCount: number = 0;
|
||||||
for (let client of c.clients) {
|
for (let client of c.clients) {
|
||||||
let nonce: string = crypto.randomBytes(4).toString("hex");
|
let nonce: string = crypto.randomBytes(4).toString("hex");
|
||||||
// TODO: verify the nonce against the received reply, which we currently ignore
|
// TODO: verify the nonce against the received reply, which we currently ignore
|
||||||
client.directMessage(JSON.stringify({
|
client.directMessage({
|
||||||
message_type: "broadcast",
|
message_type: "broadcast",
|
||||||
message: { event_type: event, seq_id: nonce, examId: archMsg.examId }
|
message: { event_type: event, seq_id: nonce, examId: archMsg.examId }
|
||||||
}));
|
});
|
||||||
dmCount++;
|
dmCount++;
|
||||||
}
|
}
|
||||||
// dmCount of 1 would be normal. more than 1 is odd, but not necessarily bad. 0 means Exam UI has gone away somehow.
|
// dmCount of 1 would be normal. more than 1 is odd, but not necessarily bad. 0 means Exam UI has gone away somehow.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user