Compare commits

...

10 Commits

Author SHA1 Message Date
Nick
3b723cbad6
Merge pull request #31 from yardstick/feature/QUANT-953
QUANT-953: remove `CustomChannel`
2023-08-03 18:01:46 -04:00
Nick Darrell
b050f709c9 version bump 2023-08-01 17:46:58 +00:00
Nick Darrell
bea6ab8e09 rm CustomChannel 2023-08-01 17:41:16 +00:00
Nick Darrell
1ce0aaf927 npm i bumped packages 2023-08-01 17:41:06 +00:00
Jarred Hunter
c8e4b690c5
Feature/dev 13474 kaniko braid (#24)
* initial build

* initial build

* attempting to implement pod template

* updated code adding braid pod template
2022-03-16 14:06:16 -05:00
Maciek Nowacki
ce2c65544c
Merge pull request #14 from yardstick/feature/QUANT-359-archimedes-integration-build-cors-compliant-web-server
QUANT-359 implement XhrListener to forward POSTs to WS
2021-04-01 10:54:34 -06:00
Maciek Nowacki
7ba8716917 compare auth token against SHA-1 HMAC of POST body 2021-03-12 15:10:49 -07:00
Maciek Nowacki
473fa4b662 set default auth token to SHA-1 of empty string 2021-03-12 12:25:22 -07:00
Maciek Nowacki
97a746c572 don't double-encode JSON 2021-03-12 11:08:19 -07:00
Maciek Nowacki
0a89138a79 compare channel id on value basis 2021-03-12 10:26:43 -07:00
10 changed files with 77 additions and 87 deletions

93
Jenkinsfile vendored
View File

@ -1,47 +1,52 @@
def label = "node-${UUID.randomUUID().toString()}"
podTemplate(label: label, inheritFrom: 'base', , containers: [
containerTemplate(name: 'base', image: 'taylorchristieyardstick/build-base:node')
]) {
node(label) {
stage('Checkout Project') {
container('base') {
checkout scm
}
}
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}"
pipeline {
agent {
kubernetes {
label "kaniko-${UUID.randomUUID()}"
inheritFrom 'kaniko'
containerTemplate {
name 'braid'
image 'node:8'
command 'sleep 9999'
}
}
}
}
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:]]*)')
"""
}
}
}
}
}
}

View File

@ -1,4 +1,4 @@
# BRAID v1.3.0
# BRAID v1.3.1
> Websocket server for the Measure platform
[![Build Status](https://semaphoreci.com/api/v1/projects/7767f0f3-4da6-4c84-9167-4db5402a3262/2573412/badge.svg)](https://semaphoreci.com/yardstick/braid)

View File

@ -1 +1 @@
1.3.0
1.3.1

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "braid",
"version": "1.2.3",
"version": "1.2.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "braid",
"version": "1.2.2",
"version": "1.3.1",
"description": "",
"main": "index.js",
"scripts": {

View File

@ -1,6 +1,5 @@
import PublicChannel from './channels/types/publicChannel';
import PrivateChannel from './channels/types/privateChannel';
import CustomChannel from './channels/types/customChannel';
import PublicClient from './clients/types/publicClient';
import PrivateClient from './clients/types/privateClient';
import CustomClient from './clients/types/customClient';
@ -17,8 +16,8 @@ class ChannelManager {
}
createChannel(data: any) {
const channelExists: PublicChannel|PrivateChannel|CustomChannel|null = this.channelExists(data.channel);
let channel: PublicChannel|PrivateChannel|CustomChannel;
const channelExists: PublicChannel|PrivateChannel|null = this.channelExists(data.channel);
let channel: PublicChannel|PrivateChannel;
if (channelExists) {
channel = channelExists;
@ -43,7 +42,7 @@ class ChannelManager {
}
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) {
channel.addClient(client);
@ -54,7 +53,7 @@ class ChannelManager {
}
}
updateChannelContent(channel: PrivateChannel|PrivateChannel|CustomChannel, channelContent: JSON) {
updateChannelContent(channel: PrivateChannel|PrivateChannel, channelContent: JSON) {
if (channel) {
channel.channelContent = channelContent;
return {status: 'success'};
@ -70,12 +69,10 @@ class ChannelManager {
`attempting to create channel of type ${data.channel_type}, channel id: ${data.channel}...`
);
if (data.channel_type === 'public') {
return new PublicChannel(data.channel);
} else if (data.channel_type === 'private') {
if (data.channel_type === 'private') {
return new PrivateChannel(data.channel);
} else {
return new CustomChannel(data.channel, data.custom);
return new PublicChannel(data.channel);
}
} catch (e) {
console.log(e);

View File

@ -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;

View File

@ -3,7 +3,6 @@ import ClientManager from '../clientManager';
import ChannelManager from '../channelManager';
import PublicChannel from '../channels/types/publicChannel';
import PrivateChannel from '../channels/types/privateChannel';
import CustomChannel from '../channels/types/customChannel';
const messageManager = require('../messageManager');
const logger = require('../logger');
@ -12,7 +11,7 @@ class ClientBase {
ws: WebSocket;
data: any;
id: number;
channel: PublicChannel|PrivateChannel|CustomChannel|null;
channel: PublicChannel|PrivateChannel|null;
clientManager: ClientManager;
channelManager: ChannelManager;
roles: string[];
@ -68,7 +67,7 @@ class ClientBase {
return this.data.client;
}
connectToChannel(channel: PublicChannel|PrivateChannel|CustomChannel) {
connectToChannel(channel: PublicChannel|PrivateChannel) {
this.channel = channel;
this.ws.on('message', this.messageListener);
this.ws.on('close', this.closeListener);

View File

@ -16,5 +16,5 @@ module.exports = {
algorithm: ['HS256']
},
messageTypes : ['broadcast', 'direct', 'changeChannel'],
archimedesToken: process.env.ARCHIMEDES_INTEGRATION_TOKEN || 'sha1=baddbeef'
archimedesToken: process.env.ARCHIMEDES_INTEGRATION_TOKEN || 'testkeyformeasure'
};

View File

@ -11,7 +11,7 @@ const AUTH_TOKEN = app.archimedesToken;
const HTTP_PORT = app.port;
import { Server, IncomingMessage, ServerResponse } from 'http'
import * as bodyParser from "body-parser";
interface ArchimedesMessage {
examId: string,
reservation_no: string
@ -25,8 +25,12 @@ class XhrListener {
this.#cm = cm;
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
app.options('*', cors());
@ -39,19 +43,22 @@ class XhrListener {
app.post('/event/pause', cors(),
(req: any, res: any, next: any) => this.eventFromArchimedes.bind(this)("pauseExam", req, res));
server.on('request', app);
// 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}`));
}
public authorized(authOffered: String): boolean {
return AUTH_TOKEN === authOffered;
public authorized(authOffered: String, rawBody: Buffer): boolean {
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) {
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) {
res.status(401);
@ -74,15 +81,15 @@ class XhrListener {
public relayEvent(event: string, archMsg: ArchimedesMessage): boolean {
logger.debugLog.info(`XhrListener:relayEvent(event: ${event}, channel: ${archMsg.reservation_no})`);
for (let c of this.#cm.channels) {
if (archMsg.reservation_no === c.id) {
if (archMsg.reservation_no == c.id) {
let dmCount: number = 0;
for (let client of c.clients) {
let nonce: string = crypto.randomBytes(4).toString("hex");
// TODO: verify the nonce against the received reply, which we currently ignore
client.directMessage(JSON.stringify({
client.directMessage({
message_type: "broadcast",
message: { event_type: event, seq_id: nonce, examId: archMsg.examId }
}));
});
dmCount++;
}
// dmCount of 1 would be normal. more than 1 is odd, but not necessarily bad. 0 means Exam UI has gone away somehow.