Skip to content
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

Local login #4

Merged
merged 2 commits into from
Sep 28, 2023
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ nodedata/

deb-package/dist/**

.env

dela/
bin/
nodes/
190 changes: 190 additions & 0 deletions scripts/run_local.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#!/bin/bash -e
# This puts all the different steps of initializing a Dela d-voting network into one shell script.
# This can be used for development by calling the script and then testing the result locally.
# The script must be called from the root of the github tree, else it returns an error.
# If the script is called with `./scripts/run_local.sh clean`, it stops all services.
# For development, the calls to the different parts can be adjusted, e.g., comment all but
# `start_backend` to only restart the backend.

if [[ $(git rev-parse --show-toplevel) != $(pwd) ]]; then
echo "ERROR: This script must be started from the root of the git repo"
exit 1
fi

asdf_shell() {
if ! asdf list "$1" | grep -wq "$2"; then
asdf install "$1" "$2"
fi
asdf local "$1" "$2"
}
asdf_shell nodejs 16.20.2
asdf_shell golang 1.21.0
mkdir -p nodes

function build_dela() {
echo "Building dela-node"
if ! [[ -d dela/ ]]; then
git clone -b fix-bbolt https://github.com/dedis/dela.git
fi
export GOBIN=$(pwd)/bin
PATH="$PATH":"$GOBIN"
if ! [[ -f $GOBIN/crypto ]]; then
(cd dela/cli/crypto && go install)
fi
if ! [[ -f $GOBIN/dvoting ]]; then
go install ./cli/dvoting
fi

echo "Installing node directories"
for d in backend frontend; do
DIR=web/$d
if ! [[ -d $DIR/node_modules ]]; then
(cd $DIR && npm ci)
fi
done
}

function keypair() {
if ! [[ "$PUBLIC_KEY" ]]; then
if ! [[ -f nodes/keypair ]]; then
echo "Getting keypair"
(cd web/backend && npm run keygen) | tail -n 2 >nodes/keypair
fi
. nodes/keypair
export PUBLIC_KEY PRIVATE_KEY
fi
}

function kill_nodes() {
pkill dvoting || true
rm -rf nodes/node*
}

function init_nodes() {
kill_nodes
keypair

echo "Starting nodes"
for n in $(seq 4); do
NODEPORT=$((2000 + n * 2))
PROXYPORT=$((2001 + n * 2))
NODEDIR=./nodes/node-$n
mkdir -p $NODEDIR
rm -f $NODEDIR/node.log
dvoting --config $NODEDIR start --postinstall --proxyaddr :$PROXYPORT --proxykey $PUBLIC_KEY \
--listen tcp://0.0.0.0:$NODEPORT --public http://localhost:$NODEPORT --routing tree --noTLS |
ts "Node-$n: " | tee $NODEDIR/node.log &
done

echo "Waiting for nodes to start up"
for n in $(seq 4); do
NODEDIR=./nodes/node-$n
while ! [[ -S $NODEDIR/daemon.sock && -f $NODEDIR/node.log && $(cat $NODEDIR/node.log | wc -l) -ge 2 ]]; do
sleep .2
done
done
}

function init_dela() {
echo "Initializing dela"
echo " Share the certificate"
for n in $(seq 2 4); do
TOKEN_ARGS=$(dvoting --config ./nodes/node-1 minogrpc token)
NODEDIR=./nodes/node-$n
dvoting --config $NODEDIR minogrpc join --address //localhost:2002 $TOKEN_ARGS
done

echo " Create a new chain with the nodes"
for n in $(seq 4); do
NODEDIR=./nodes/node-$n
# add node to the chain
MEMBERS="$MEMBERS --member $(dvoting --config $NODEDIR ordering export)"
done
dvoting --config ./nodes/node-1 ordering setup $MEMBERS

echo " Authorize the signer to handle the access contract on each node"
for s in $(seq 4); do
NODEDIR=./nodes/node-$s
IDENTITY=$(crypto bls signer read --path $NODEDIR/private.key --format BASE64_PUBKEY)
for n in $(seq 4); do
NODEDIR=./nodes/node-$n
dvoting --config $NODEDIR access add --identity "$IDENTITY"
done
done

echo " Update the access contract"
for n in $(seq 4); do
NODEDIR=./nodes/node-$n
IDENTITY=$(crypto bls signer read --path $NODEDIR/private.key --format BASE64_PUBKEY)
dvoting --config ./nodes/node-1 pool add --key ./nodes/node-1/private.key --args go.dedis.ch/dela.ContractArg \
--args go.dedis.ch/dela.Access --args access:grant_id \
--args 0300000000000000000000000000000000000000000000000000000000000000 --args access:grant_contract \
--args go.dedis.ch/dela.Evoting --args access:grant_command --args all --args access:identity --args $IDENTITY \
--args access:command --args GRANT
done
}

function kill_db() {
docker rm -f postgres_dvoting || true
}

function init_db() {
kill_db

echo "Starting postgres database"
docker run -d -v "$(pwd)/web/backend/src/migration.sql:/docker-entrypoint-initdb.d/init.sql" \
-e POSTGRES_PASSWORD=$DATABASE_PASSWORD -e POSTGRES_USER=$DATABASE_USERNAME \
--name postgres_dvoting -p 5432:5432 postgres:15 >/dev/null

echo "Adding SCIPER to admin"
(cd web/backend && npx ts-node src/cli.ts addAdmin --sciper $SCIPER_ADMIN | grep -v Executing)
}

function kill_backend() {
pkill -f "web/backend" || true
}

function start_backend() {
kill_backend
keypair

echo "Running backend"
(cd web/backend && npm run start-dev | ts "Backend: " &)
}

function kill_frontend() {
pkill -f "web/frontend" || true
}

function start_frontend() {
kill_frontend
keypair

echo "Running frontend"
(cd web/frontend && npm run start-dev | ts "Frontend: " &)
}

export SCIPER_ADMIN=100100
export DATABASE_USERNAME=dvoting
export DATABASE_PASSWORD=postgres
export FRONT_END_URL="http://localhost:3000"
export DELA_NODE_URL="http://localhost:2003"
export BACKEND_HOST="localhost"
export BACKEND_PORT="6000"
export SESSION_SECRET="session secret"
export REACT_APP_NOMOCK=on

if [[ "$1" == "clean" ]]; then
kill_frontend
kill_nodes
kill_backend
kill_db
exit
fi

build_dela
init_nodes
init_dela
init_db
start_backend
start_frontend
1 change: 1 addition & 0 deletions web/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"type": "commonjs",
"scripts": {
"start": "ts-node -r dotenv/config src/Server.ts dotenv_config_path=config.env",
"start-dev": "NODE_ENV=development ts-node -r dotenv/config src/Server.ts dotenv_config_path=config.env",
"dev": "./node_modules/.bin/nodemon",
"eslint": "./node_modules/.bin/eslint .",
"eslint-fix": "./node_modules/.bin/eslint . --fix",
Expand Down
11 changes: 11 additions & 0 deletions web/backend/src/authManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const PERMISSIONS = {
};

let authEnforcer: Enforcer;

/*
We use the postgres adapter to store the Casbin policies
we initialize the adapter with the connection string and the migrate option
Expand Down Expand Up @@ -83,3 +84,13 @@ export function setMapAuthorization(list: string[][]): Map<String, Array<String>
}
return userRights;
}

// Reads a SCIPER from a string and returns the number. If the SCIPER is not in
// the range between 100000 and 999999, an error is thrown.
export function readSCIPER(s: string): number {
const n = parseInt(s, 10);
if (n < 100000 || n > 999999) {
throw new Error(`SCIPER is out of range. ${n} is not between 100000 and 999999`);
}
return n;
}
13 changes: 5 additions & 8 deletions web/backend/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { SequelizeAdapter } from 'casbin-sequelize-adapter';
import { newEnforcer } from 'casbin';
import { curve } from '@dedis/kyber';
import * as fs from 'fs';
import { PERMISSIONS } from './authManager';
import { PERMISSIONS, readSCIPER } from './authManager';

const program = new Command();

Expand Down Expand Up @@ -95,16 +95,13 @@ program
const scipers: Array<string> = data.split('\n');
const policies = [];
for (let i = 0; i < scipers.length; i += 1) {
const sciper: number = Number(scipers[i]);
if (Number.isNaN(sciper)) {
throw new InvalidArgumentError(`SCIPER '${sciper}' on line ${i + 1} is not a number`);
}
if (sciper > 999999 || sciper < 100000) {
try {
policies[i] = [readSCIPER(scipers[i]), electionId, PERMISSIONS.ACTIONS.VOTE];
} catch (e) {
throw new InvalidArgumentError(
`SCIPER '${sciper}' on line ${i + 1} is outside acceptable range (100000..999999)`
`SCIPER '${scipers[i]}' on line ${i + 1} is not a valid sciper: ${e}`
);
}
policies[i] = [scipers[i], electionId, PERMISSIONS.ACTIONS.VOTE];
}
const enforcer = await initEnforcer();
await enforcer.addPolicies(policies);
Expand Down
33 changes: 32 additions & 1 deletion web/backend/src/controllers/authentication.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
import express from 'express';
import axios, { AxiosError } from 'axios';
import { sciper2sess } from '../session';
import { getUserPermissions, setMapAuthorization } from '../authManager';
import { getUserPermissions, readSCIPER, setMapAuthorization } from '../authManager';

export const authenticationRouter = express.Router();

authenticationRouter.get('/get_dev_login', (req, res) => {
if (process.env.NODE_ENV !== 'development') {
const err = `/get_dev_login can only be called in development: ${process.env.NODE_ENV}`;
console.error(err);
res.status(500).send(err);
return;
}
if (process.env.SCIPER_ADMIN === undefined) {
const err = 'Please set SCIPER_ADMIN for /get/dev/login endpoint';
console.error(err);
res.status(500).send(err);
return;
}
try {
req.session.userId = readSCIPER(process.env.SCIPER_ADMIN);
req.session.lastName = 'develo';
req.session.firstName = 'pment';
} catch (e) {
const err = `Invalid SCIPER_ADMIN: ${e}`;
console.error(err);
res.status(500).send(err);
return;
}

const sciperSessions = sciper2sess.get(req.session.userId) || new Set<string>();
sciperSessions.add(req.sessionID);
sciper2sess.set(req.session.userId, sciperSessions);

res.redirect('/logged');
});

// This is via this endpoint that the client request the tequila key, this key
// will then be used for redirection on the tequila server
authenticationRouter.get('/get_teq_key', (req, res) => {
Expand Down
10 changes: 5 additions & 5 deletions web/backend/src/controllers/users.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import express from 'express';

import { isAuthorized, PERMISSIONS } from '../authManager';
import { isAuthorized, PERMISSIONS, readSCIPER } from '../authManager';

export const usersRouter = express.Router();

Expand All @@ -26,13 +26,13 @@ usersRouter.post('/add_role', (req, res, next) => {
return;
}

const { sciper } = req.body;

// The sciper has to contain 6 numbers
if (sciper > 999999 || sciper < 100000) {
try {
readSCIPER(req.body.sciper);
} catch (e) {
res.status(400).send('Sciper length is incorrect');
return;
}

next();
// Call https://search-api.epfl.ch/api/ldap?q=228271, if the answer is
// empty then sciper unknown, otherwise add it in userDB
Expand Down
1 change: 1 addition & 0 deletions web/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"scripts": {
"start": "HTTPS=true react-scripts start",
"start-dev": "react-scripts start",
"build": "HTTPS=true react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
Expand Down
1 change: 1 addition & 0 deletions web/frontend/src/components/utils/Endpoints.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// information accessed through the middleware
export const ENDPOINT_GET_TEQ_KEY = '/api/get_teq_key';
export const ENDPOINT_DEV_LOGIN = '/api/get_dev_login';
export const ENDPOINT_PERSONAL_INFO = '/api/personal_info';
export const ENDPOINT_LOGOUT = '/api/logout';
export const ENDPOINT_USER_RIGHTS = '/api/user_rights';
Expand Down
11 changes: 9 additions & 2 deletions web/frontend/src/pages/session/HandleLogin.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { ENDPOINT_GET_TEQ_KEY } from 'components/utils/Endpoints';
import { ENDPOINT_DEV_LOGIN, ENDPOINT_GET_TEQ_KEY } from 'components/utils/Endpoints';
import { FlashLevel, FlashState } from 'index';

// The backend will provide the client the URL to make a Tequila authentication.
// We therefore redirect to this address.
const handleLogin = async (fctx: FlashState) => {
try {
const res = await fetch(ENDPOINT_GET_TEQ_KEY);
let res;
if (process.env.NODE_ENV === 'development') {
await fetch(ENDPOINT_DEV_LOGIN);
window.location.reload();
return;
} else {
res = await fetch(ENDPOINT_GET_TEQ_KEY);
}

const d = new Date();
d.setTime(d.getTime() + 120000);
Expand Down
Loading